爬取百度百科[scrapy启发]

来源:互联网 发布:绝食瘦下来知乎 编辑:程序博客网 时间:2024/05/29 18:53

摘要:主要是基于业务的需要,要一批词,学习了scrapy,借鉴scrapy的一点点思想,写了一个临时爬虫。

一开始,是采用scrapy来写的,可是对于一个框架不熟悉,需要要花时间学习;还有一个主要的,好像代码并不会因为用了这个框架少了多少,可能抓取大量的会有优势。还有一个,我的研究业务单一,就是想要一批词,并且现在就想要,来做一个研究。还有一个, scrapy的异常机制还未找到怎么应用,当出现一些错误它就运行停止了,受制于scrapy。如果把它的源码看完估计用起它会好很多,因为网上的资料都是基础的入门篇,什么代理,什么头配置,什么cookie,什么异常处理都要分别查找。在尝试scrapy之后,未果,不过可把它思想搬过来用一下,然后写了一个临时的任务。

1. 实体类

定义实体类,其实这个是字典,就两个字段,一个词,一个是url;

class BaiduBaikeItem(scrapy.Item):    keyword = scrapy.Field()    url = scrapy.Fiel)

2. spider

在写scrapy的时候,也用到了yield这个东西,这个是生成器,当程序运行到yield语句时,会返回到next或send或for循环或异常那里的。这个可以查看上一篇《python[变量作用域-函数-闭包-装饰器-生成器]》。
还有一个细节,这里用到一个from urllib.parse import urljoin函数,这个函数可以帮助我们节省很多拼url的代码,这个是在scrapy中发现的。
对于选择器的解释,scrapy里面封装了一个,这里采用了from bs4 import BeautifulSoup这个,这个东西对于HMTL标签解释挺强大的。
对于url请求,采用了requests这个包,它可请求get,post等http协议请求。
保存是把字典转成json,然后把json采用codecs来写文件。

# coding=utf-8import codecsimport jsonimport randomimport timefrom urllib.parse import urljoinimport requestsfrom bs4 import BeautifulSoupfrom items import BaiduBaikeItemfrom crawDataWithProxy import proxy_handler# 根列表cataUrls = {    'http://baike.baidu.com/fenlei/%E5%81%A5%E5%BA%B7%E7%A7%91%E5%AD%A6': '健康科学',}baikeHeaders = {    'Host': 'baike.baidu.com',    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0',    'Accept': '*/*',    'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',    'Accept-Encoding': 'gzip,deflate',    'Cache-Control': 'no-cache',    'Content-Type': 'application/x-www-form-urlencoded',    'Content-Length': '0',    'Connection': 'keep-alive',    'Upgrade-Insecure-Requests': '1'}# 是否使用代理proxy = False# 重爬次数M = 10COUNT = 0# get responsedef r_get(url, isproxy=False):    time.sleep(random.random() * 0.5)    r = None    try:        if not isproxy:            r = requests.get(url, timeout=20, headers=baikeHeaders)        else:            r = requests.get(url, timeout=20, proxies=proxy_handler, cookies=None)    except Exception as e:        print(e)    if (r is not None and r.status_code != 200):        r = None    return r# 重试def retry(n, url):    s = random.random() * (2 * n) + 1    print('%dth times, %f秒后重试。' % (n, s))    time.sleep(s)    response = r_get(url, isproxy=proxy)    return (response, n + 1)# 解释,这个函数用来递归def myparse(url, page=0, catalog=1):    global COUNT    n = 0    page += 1    if page == 1:        print('##########(catalog:%s(%s),page:%d)#(total itmes:%d)###########' % (cataUrls[url], url, page, COUNT))    else:        print('##########(catalog:%d,page:%d(%s)#(total items:%d)###########' % (catalog, page, url, COUNT))    response = r_get(url, isproxy=proxy)    # 当抓取失败后,可进行多次抓取    while True:        if response == None:            rs = retry(n, url)            response = rs[0]            n = rs[1]            if n < M:                continue            else:                print('已经进行了%d次的重试..%s' % (M, url))                break        else:            response.encoding = 'utf-8'            htmls = response.text            base = response.url            htmlbf = BeautifulSoup(htmls, "lxml")            i = 0            try:                itemlist = htmlbf.select(".grid-list > ul > li > div.list > a")            except Exception as e:                print(e)                itemlist = []            # 对于空情况,要考虑是否重试            if len(itemlist) == 0:                if u'百度百科错误页' in htmlbf.text:                    print(u'百度百科错误页...')                    response = None                    continue                else:                    print('%s request has no items.' % (url))            else:                # 解释每个关键词                for quote in itemlist:                    b_item = BaiduBaikeItem()                    b_item['keyword'] = quote.text                    b_item['url'] = urljoin(base, quote['href'])                    i += 1                    print('count of item:%d' % (i))            # 产生item,这个跳出去让这个item进行保存起来,这个scrapy也是这样实现的                    yield b_item                COUNT = COUNT + i        # 找到下一页的网址,让它递归就可以了            print('# 下一页')            try:                next_sr = htmlbf.select('#next')                next_page_url = next_sr[0]['href']            except Exception as e:                print('下一页 Exception %s' % (e))                next_page_url = None            if next_page_url is not None:                print("准备下一页")        # 这个算是一个生成器嵌套着一个生成器了,虽然是自已调用了自己                g = myparse(url=urljoin(base, next_page_url), page=page, catalog=catalog)                for item in g:                    yield item            else:                pass            # 如果是第一页的时才会从分类的目录进行前进,找到下一级目录的URL,让它递归            if page == 1:                print("准备下一级目录")                try:                    categoryx = htmlbf.select(".p-category > div > span")[0].text                except Exception as e:                    print('has no 下一级目录,e = %s, url = %s' % (e, url))                    categoryx = None                if categoryx is not None and u'下级分类' in categoryx:                    next_sr = htmlbf.select(".p-category > div > a")                    for next_catalog in next_sr:                        c_url = next_catalog['href']                        txt = next_catalog.text                        if c_url is not None:                            whole_url = urljoin(base, c_url)                # 排除重复的url                            if whole_url not in cataUrls:                                cataUrls[whole_url] = txt                                c_item = BaiduBaikeItem()                                c_item['keyword'] = txt                                c_item['url'] = whole_url                # 希望对这个分类也保存一下                                yield c_item                # 开始下一个分类                                catalog += 1                                print('will claw (%s:%s)' % (txt, c_url))                # 嵌套生成器,遍历时就调用了这个生成器                                g = myparse(url=whole_url, page=0, catalog=catalog)                                for item in g:                                    yield item                            else:                                print('(%s,%s) has be crawed..will skip it ' % (txt, c_url))                else:                    print('this is not 下级分类, skip...')            else:                pass            breakif __name__ == '__main__':    file = codecs.open('%s.json' % ('baike'), 'w+', encoding='utf-8')    # 这里要用一个副本做存出来,不然后说cataUrls是变的提示    start_urls = list(cataUrls.keys())    for url in start_urls:        g = myparse(url)        for item in g:            # print(item)            line = json.dumps(dict(item), ensure_ascii=False) + "\n"            file.write(line)            file.flush()    file.close()    for k, v in cataUrls.items():        print(k, v)

3. 代理格式

可以设置一下请求代理,格式如下,像上面那样引用就可以了。

proxyHost = "地址"proxyPort = "端口"proxyUser = "用户名"proxyPass = "密码"proxyMeta = "http://%(user)s:%(pass)s@%(host)s:%(port)s" % {    "host": proxyHost,    "port": proxyPort,    "user": proxyUser,    "pass": proxyPass,}proxy_handler = {    "http": proxyMeta,    "https": proxyMeta,}

本试探性研究到这里,仅作为学习研究参考,才疏深浅,请指教。。

【作者:happyprince, http://blog.csdn.net/ld326/article/details/78757223】