网络爬虫之 scrapy 框架详解
twisted 介绍
皇冠体育二代信用盘带手机版 http://hxforum.com/thread-422-1-1.html QQ2952777280
Twisted 是用 Python 实现的基于事件驱动的网络引擎框架, scrapy 正是依赖于 twisted,
它是基于事件循环的异步非阻塞网络框架, 可以实现爬虫的并发.
twisted 是什么以及和 requests 的区别:
request 是一个 python 实现的可以伪造浏览器发送 Http 请求的模块, 它封装了 socket 发送请求
twisted 是基于时间循环的异步非阻塞的网络框架, 它也封装了 socket 发送请求, 但是他可以单线程的完成并发请求.
twisted 的特点是:
非阻塞: 不等待
异步: 回调
事件循环: 一直循环去检查状态
scrapy 的 pipeline 文件和 items 文件
这两个文件有什么作用
先看看我们上篇的示例:
View Code
在这个示例中, 虽然我们已经通过 chouti.py 一个文件中的 parse 方法实现了爬去抽屉网的新闻并将之保存在文件中的功能,
但是我们会发现有两个问题:
1, 在循环爬去每一页的时候, 每次都需要重新打开然后再关闭文件, 如果数据量庞大的话, 这对性能有很大的影响.
2, 我们将解析和数据持久化都放在了同一个文件的同一个方法中, 没有做到分工明确
如果要解决这两个问题, 则需要用到 scrapy 自动为我们生成的 pipeline 文件和 items 文件
这两个文件怎么用
如果我们要使用这两个文件从而解决问题, 则需要有四部操作:
a. 编写 pipeline 文件中的类, 格式如下:
- class XXXPipeline(object):
- def process_item(self, item, spider):
- return item
b. 编写 items 文件中的类, 格式如下:
- class XXXItem(scrapy.Item):
- href = scrapy.Field()
- title = scrapy.Field()
c. 配置 settings 文件
- ITEM_PIPELINES = {
- 'xxx.pipelines.XXXPipeline': 300,
- 'xxx.pipelines.XXXPipeline2': 600, # 后面的数字为优先级, 数字越大, 优先级月底
- }
d. 在 parse 方法中 yield 一个 Item 对象
- from xxx.items import XXXItem
- def parse(self, response):
- ...
- yield XXXItem(text=text,href=href)
执行流程为:
当我们在执行爬虫中的 parse 方法的时候, scrapy 一旦解析到有 yield XXXitem 的语句, 就会到配置文件中找
ITEM_PIPELINES 的配置项, 进而找到 XXXPipeline 类, 然后执行其中的方法, 我们就可以在方法中做很多操作
当然, pipeline 中不止 process_item 一个方法.
Pipeline 中的方法详解
- class FilePipeline(object):
- def __init__(self,path):
- self.f = None
- self.path = path
- @classmethod
- def from_crawler(cls, crawler):
- """
- 初始化时候, 用于创建 pipeline 对象
- :param crawler:
- :return:
- """
- # 从配置文件中获取配置好的文件存放目录
- path = crawler.settings.get('HREF_FILE_PATH')
- return cls(path)
- def open_spider(self,spider):
- """
- 爬虫开始执行时, 调用
- :param spider:
- :return:
- """ self.f = open(self.path,'a+')
- def process_item(self, item, spider):
- # 在这里做持久化
- self.f.write(item['href']+'\n')
- return item # 交给下一个 pipeline 的 process_item 方法
- # raise DropItem()# 如果写上这一句, 后续的 pipeline 的 process_item 方法不再执行
- def close_spider(self,spider):
- """
- 爬虫关闭时, 被调用
- :param spider:
- :return:
- """
- self.f.close()
- 去重
- scrapy 内部实现的去重
- 从上一篇的例子我们可以看出, 其实 scrapy 内部在循环爬去页码的时候, 已经帮我们做了去重功能的,
- 因为我们在首页可以看到 1,2,3,4,5,6,7,8,9,10 页的页码以及连接, 当爬虫爬到第二页的时候,
- 还是可以看到这 10 个页面及连接, 然后它并没有再重新把第一页爬一遍.
- 它内部实现去重的原理是, 将已爬去的网址存入一个 set 集合里, 每次爬取新页面的时候就先看一下是否在集合里面
- 如果在, 就不再爬去, 如果不在就爬取, 然后再添加入到 set 里. 当然, 这个集合存放的不是原网址,
- 而是将链接通过 request_fingerprint() 方法将它变成一个类似于 md5 的值, 这样可以节省存储空间
- 自定义去重
- 虽然 scrapy 已经帮我们实现了去重, 但是有时候不足以满足我们的需求, 这样就需要我们自定义去重了
- 自定义去重分两步
- 1, 编写 DupeFilter 类
- from scrapy.dupefilter import BaseDupeFilter
- from scrapy.utils.request import request_fingerprint
- class XXXDupeFilter(BaseDupeFilter):
- def __init__(self):
- '''初始化一个集合, 用来存放爬去过的网址'''
- self.visited_fd = set()
- @classmethod
- def from_settings(cls, settings):
- '''
- 如果我们自定义了 DupeFilter 类并且重写了父类的该方法,
- scrapy 会首先执行该方法, 获取 DupeFilter 对象,
- 如果没有定义, 则会执行 init 方法来获取对象
- '''
- return cls()
- def request_seen(self, request):
- '''在此方法中做操作, 判断以及添加网址到 set 里'''
- # 将 request 里的 url 转换下, 然后判断是否在 set 里
- fd = request_fingerprint(request=request)
- # 循环 set 集合, 如果已经在集合里, 则返回 True, 爬虫将不会继续爬取该网址
- if fd in self.visited_fd:
- return True
- self.visited_fd.add(fd)
- def open(self): # can return deferred
- '''开始前执行此方法'''
- print('开始')
- def close(self, reason): # can return a deferred
- '''结束后执行此方法'''
- print('结束')
- def log(self, request, spider): # log that a request has been filtered
- '''在此方法中可以做日志操作'''
- print('日志')
2. 配置 settings 文件
1
2
3
修改默认的去重规则
- DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
- DUPEFILTER_CLASS = 'xxx.dupefilters.XXXDupeFilter'
深度
深度就是爬虫所要爬取的层级
限制深度只需要配置一下即可
1
2
限制深度
- DEPTH_LIMIT = 3
- cookie
获取上一次请求之后获得的 cookie
- from scrapy.http.cookies import CookieJar
- class ChoutiSpider(scrapy.Spider):
- name = 'chouti'
- allowed_domains = ['chouti.com']
- start_urls = [ http://hxforum.com/thread-422-1-1.html/ ']
- cookie_dict = {}
- def parse(self, response):
- # 去响应头中获取 cookie,cookie 保存在 cookie_jar 对象
- cookie_jar = CookieJar()
- cookie_jar.extract_cookies(response, response.request)
- # 去对象中将 cookie 解析到字典
- for k, v in cookie_jar._cookies.items():
- for i, j in v.items():
- for m, n in j.items():
- self.cookie_dict[m] = n.value
再次请求的时候携带 cookie
- yield Request(
- url='http://hxforum.com/thread-422-1-1.html',
- method='POST',
- body="phone=861300000000&password=12345678&oneMonth=1",#
- cookies=self.cookie_dict,
- headers={
- 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
- },
- callback=self.check_login
- )
来源: http://www.bubuko.com/infodetail-2663760.html