zoukankan      html  css  js  c++  java
  • 一个网络爬虫的分析

    说明

    这个爬虫是从outofmemory看到的,只有100行,内容是抓取淘宝商品信息,包括商品名、卖家id、地区、价格等信息,json格式,作者说他曾经抓取到了一千万条信息。

    出于对这个爬虫能力的感叹,我好奇的对它进行了分析,发现原理是如此的简单,感叹python的强大之余,好也把分析的心得记录一下,引为后来的经验。

    现在这个爬虫能不能用就没有保证了,不过没有关系,只是作为一个学习的例子。

    代码

    代码可以到原来的网址下,为免失效,现张贴如下:

    import time
    import leveldb
    from urllib.parse import quote_plus 
    import re
    import json
    import itertools
    import sys
    import requests
    from queue import Queue
    from threading import Thread
    
    URL_BASE = 'http://s.m.taobao.com/search?q={}&n=200&m=api4h5&style=list&page={}'
    
    def url_get(url):
        # print('GET ' + url)
        header = dict()
        header['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
        header['Accept-Encoding'] = 'gzip,deflate,sdch'
        header['Accept-Language'] = 'en-US,en;q=0.8'
        header['Connection'] = 'keep-alive'
        header['DNT'] = '1'
        #header['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36'
        header['User-Agent'] = 'Mozilla/12.0 (compatible; MSIE 8.0; Windows NT)'
        return requests.get(url, timeout = 5, headers = header).text
    
    def item_thread(cate_queue, db_cate, db_item):
        while True:
            try:
                cate = cate_queue.get()
                post_exist = True
                try:
                    state = db_cate.Get(cate.encode('utf-8'))
                    if state != b'OK': post_exist = False
                except:
                    post_exist = False
                if post_exist == True:
                    print('cate-{}: {} already exists ... Ignore'.format(cate, title))
                    continue
                db_cate.Put(cate.encode('utf-8'), b'crawling')
                for item_page in itertools.count(1):
                    url = URL_BASE.format(quote_plus(cate), item_page)
                    for tr in range(5):
                        try:
                            items_obj = json.loads(url_get(url))
                            break
                        except KeyboardInterrupt:
                            quit()
                        except Exception as e:
                            if tr == 4: raise e
                    if len(items_obj['listItem']) == 0: break                        
                    for item in items_obj['listItem']:
                        item_obj = dict(
                            _id = int(item['itemNumId']),
                            name = item['name'],
                            price = float(item['price']),
                            query = cate,
                            category = int(item['category']) if item['category'] != '' else 0,
                            nick = item['nick'],
                            area = item['area'])
                        db_item.Put(str(item_obj['_id']).encode('utf-8'),
                                    json.dumps(item_obj, ensure_ascii = False).encode('utf-8'))
    
                    print('Get {} items from {}: {}'.format(len(items_obj['listItem']), cate, item_page))
    
                    if 'nav' in items_obj:
                        for na in items_obj['nav']['navCatList']:
                            try:
                                db_cate.Get(na['name'].encode('utf-8'))
                            except:
                                db_cate.Put(na['name'].encode('utf-8'), b'waiting')
                db_cate.Put(cate.encode('utf-8'), b'OK')
                print(cate, 'OK')
            except KeyboardInterrupt:
                break
            except Exception as e:
                print('An {} exception occured'.format(e))
    
    def cate_thread(cate_queue, db_cate):
        while True:
            try:
                for key, value in db_cate.RangeIter():
                    if value != b'OK':
                        print('CateThread: put {} into queue'.format(key.decode('utf-8')))
                        cate_queue.put(key.decode('utf-8'))
                time.sleep(10)
            except KeyboardInterrupt:
                break
            except Exception as e:
                print('CateThread: {}'.format(e))
    
    if __name__ == '__main__':
        db_cate = leveldb.LevelDB('./taobao-cate')
        db_item = leveldb.LevelDB('./taobao-item')
        orig_cate = '正装'
        try:
            db_cate.Get(orig_cate.encode('utf-8'))
        except:
            db_cate.Put(orig_cate.encode('utf-8'), b'waiting')
        cate_queue = Queue(maxsize = 1000)
        cate_th = Thread(target = cate_thread, args = (cate_queue, db_cate))
        cate_th.start()
        item_th = [Thread(target = item_thread, args = (cate_queue, db_cate, db_item)) for _ in range(5)]
        for item_t in item_th:
            item_t.start()
        cate_th.join()
    

      

    分析

    一个只有一百行的代码,也不用花太多心思就可以看懂了,不过其中一些有意思的心得还是可以分享下。

    1. 使用vim打开,在使用了fold功能后,可以清晰的看到代码由import部分,三个自定义函数和一个main了,所以可以直接从main开始看。
    2. main建立(也可以是已经建立的)了两个数据库,分别是db_catedb_item,还定义了开始时抓取的商品种类(category)orig_cate
    3. 先在db_cate中尝试访问下orig_cate,如果没有这个种类,就加入这个种类,属性设置为waitingleveldb就是一个key-value数据库,使用起来非常的方便。
    4. 建立一个种类的队列cate_queue,然后就建立一个种类的线程cate_th,会调用自己定义的一个函数cate_thread,参数是队列和种类数据库。
    5. 再建立5个线程item_th,调用定义的item_thread函数参数是队列和两个数据库。
    6. 最后会等待线程终止后退出。
    7. 这时就可以看前面的定义的函数cate_thread,这个函数会重复从种类数据库cate_db中遍历取出种类名,然后看这个种类是不是已经抓取过了,如果没有,就加入到种类队列cate_queue
    8. 再看函数item_thead,从种类队列cate_queue中取出一个种类,再从种类数据库中查看其状态,如果是ok,就取下一个种类;如果不是ok,就标记为crawling,然后就使用这个类别和一个遍历的序号就可以获得一个网址,然后就重复的尝试获取这个页面的数据,再分析,保存到item_db中,再把种类在cate_db中标记为ok,也就是完成,同时,把页面有的种类信息放到cate_db数据库中。
    9. 这样这个爬虫就可以一直工作了。

    总结

    这个爬虫的结构很清晰,一个数据库用来保存种类的状态信息,一个数据库保存获取到的信息,一个队列作为进程间通信的工具,数据库使用key-value,网页抓取使用requests。参考这个结构,很多爬虫都可以写出来了。

  • 相关阅读:
    BZOJ 2034 【2009国家集训队】 最大收益
    vijos P1780 【NOIP2012】 开车旅行
    BZOJ 2115 【WC2011】 Xor
    BZOJ 3631 【JLOI2014】 松鼠的新家
    BZOJ 4717 改装
    BZOJ 2957 楼房重建
    BZOJ 4034 【HAOI2015】 T2
    BZOJ 1834 【ZJOI2010】 network 网络扩容
    BZOJ 2440 【中山市选2011】 完全平方数
    BZOJ 2733 【HNOI2012】 永无乡
  • 原文地址:https://www.cnblogs.com/hustlijian/p/4353576.html
Copyright © 2011-2022 走看看