爬取百度百科[scrapy启发]

摘要:主要是基于业务的需要,要一批词,学习了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-8
import codecs
import json
import random
import time
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup

from items import BaiduBaikeItem
from 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 = 10
COUNT = 0

# get response
def 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
            break


if __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】

你可能感兴趣的:(python)