Scrapy之增量式爬虫

当我们在浏览相关网页的时候会发现,某些网站定时会在原有网页数据的基础上更新一批数据,例如某电影网站会实时更新一批最近热门的电影。小说网站会根据作者创作的进度实时更新最新的章节数据等等。那么,类似的情景,当我们在爬虫的过程中遇到时,我们是不是需要定时更新程序以便能爬取到网站中最近更新的数据呢?

增量式爬虫

  • 概念:通过爬虫程序监测某网站数据更新的情况,以便可以爬取到该网站更新出的新数据。

  • 如何进行增量式的爬取工作:

    • 在发送请求之前判断这个URL是不是之前爬取过

    • 在解析内容后判断这部分内容是不是之前爬取过

    • 写入存储介质时判断内容是不是已经在介质中存在

      • 分析:

        不难发现,其实增量爬取的核心是去重, 至于去重的操作在哪个步骤起作用,只能说各有利弊。在我看来,前两种思路需要根据实际情况取一个(也可能都用)。第一种思路适合不断有新页面出现的网站,比如说小说的新章节,每天的最新新闻等等;第二种思路则适合页面内容会更新的网站。第三个思路是相当于是最后的一道防线。这样做可以最大程度上达到去重的目的。

  • 去重方法

    • 将爬取过程中产生的url进行存储,存储在redisset中。当下次进行数据爬取时,首先对即将要发起的请求对应的url在存储的urlset中做判断,如果存在则不进行请求,否则才进行请求。
    • 对爬取到的网页内容进行唯一标识的制定,然后将该唯一表示存储至redisset中。当下次爬取到网页数据的时候,在进行持久化存储之前,首先可以先判断该数据的唯一标识在redisset中是否存在,在决定是否进行持久化存储。

示例

我们这里使用一个示例来演示增量式爬虫,我们这里原本要爬取糗事百科,无奈访问的时候正值糗事百科官方升级,随便爬取了笑话网来演示吧。

爬虫文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

import hashlib
from redis import Redis

from scrapyIncremental.items import ScrapyincrementalItem

class DuanziwangSpider(CrawlSpider):
name = 'duanziwang'
# 起始url
start_urls = ['http://xiaohua.zol.com.cn/lengxiaohua/1.html']

# 其他列表页规则
rules = (
Rule(LinkExtractor(allow=r'/lengxiaohua/\d+\.html'), callback='parse_item', follow=True),
)

# 解析每页数据
def parse_item(self, response):
# 连接redis并实例化redis
conn = Redis(host='127.0.0.7', port=6379)
# 解析笑话列表
posts = response.xpath('//*[@class="article-list"]/li')

# 循环解析每条笑话,取出每条笑话标题和内容
for post in posts:
tit = post.xpath('./span[2]/a/text()').extract_first()
content = post.xpath('./div[2]/text()').extract_first()


item = ScrapyincrementalItem()
item['tit'] = tit
item['content'] = content
# 加密该条笑话标题和内容
hash_str = hashlib.sha256((tit+content).encode()).hexdigest()
# 将哈希后的笑话串添加到redis set集合中
redis_save_res = conn.sadd('duanzi_hash', hash_str)
# 如果redis返回1说明集合中没有该条笑话,反之已经爬取过了
if redis_save_res == 1:
yield item
else:
print(f'【{tit}】已经爬取过了')

item文件

1
2
3
4
5
6
7
import scrapy

# 笑话标题和内容
class ScrapyincrementalItem(scrapy.Item):
tit = scrapy.Field()
content = scrapy.Field()

管道文件

1
2
3
4
5
6
7
8
9
10
from redis import Redis

class ScrapyincrementalPipeline(object):
def open_spider(self, spider):
self.conn = Redis(host='127.0.0.1', port=6379)
# 将该条笑话存入redis
def process_item(self, item, spider):
print('爬取到一条【%s】,正在入库......' % item['tit'])
res = self.conn.lpush('duanzi_data', str(item))
return item

爬取依次后再次爬取,已爬取过的笑话会提示爬取过了,不会再重新爬取。