欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

爬取百度百科[scrapy启发]

程序员文章站 2024-02-19 17:54:28
...

摘要:主要是基于业务的需要,要一批词,学习了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)[email protected]%(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

相关标签: 爬虫