在之前我简单的实现了 Scrapy的基本内容。
存在两个问题需要解决。
- 先爬取详情页面,在根据页面url获取图片太费事了,要进行简化,一个项目就实现图片爬取。
- 增量爬虫,网站数据更新,获取更新内容。
一般爬虫的逻辑是:给定起始页面,发起访问,分析页面包含的所有其他链接,然后将这些链接放入队列,再逐次访问这些队列,直至边界条件结束。为了针对列表页+详情页这种模式,需要对链接抽取(link extractor)的逻辑进行限定。我们先了解一下 crawlspider rules。
crawlspider rules的运行机制
request url的获取是Rule定位到的容器里,所有a标签里的href链接,比如用xpath定位。
rules = (
Rule(LinkExtractor(restrict_xpaths=("//table[@class="tbspan"]/tr/td/b/a[2]")),callback='parse_item'),
)
LinkExtractor 构造器各参数说明:https://www.cnblogs.com/xinglejun/p/10408630.html
容器下的所有a标签的链接全都会被获取到,并且侍自动发送请求,不用自己手动发送请求。响应页面是Rule请求的链接的页面内容,也就是你定位容器ul里的a标签的链接,然后你用鼠标点击该链接所看到的页面内容,在callback函数里提取数据也是在这个页面里提取,parse_item(self,response),这个函数的response对象就是这个页面内容,千万不要搞错对象了。
follow,follow就是跟进的意思,如果follow=True,那么,在你获取到的响应页面里,所以符合Rule规则的href链接都会被获取到,而且它还会自动跟进,进入到获取到的链接响应页面,在该页面又一次匹配Rule规则,看看有没有符合的,如果有又继续跟进,一直到匹配不到规则为止。
详细的解释看:https://blog.csdn.net/joe8910/article/details/85159059。
rules = (
Rule(LinkExtractor(restrict_xpaths=("//table[@class="tbspan"]/tr/td/b/a[2]")),callback='parse_item'),
Rule(LinkExtractor(allow=r'list_4_\d+\.html'), callback='parse_item', follow=True),
)
一条是获取响应页面规则,一条是获取翻页链接规则。我们运行一下试试。
class ChinesemedicineSpider(CrawlSpider):
name = 'chinesemedicine'
#allowed_domains = ['http://yzs.satcm.gov.cn']
start_urls = ['https://www.ygdy8.net/html/gndy/china/index.html']
rules = (
Rule(LinkExtractor(restrict_xpaths=("//table[@class='tbspan']/tr/td/b/a[2]")),callback='parse_item'),
Rule(LinkExtractor(allow=r'list_4_\d+\.html'), follow=True),
)
def parse_item(self, response):
item = {}
print('page: %s' % response.url)
这个是一个方法实现,但我们不选择这个,而选择另一个办法实现。
class ChinesemedicineSpider(CrawlSpider):
name = 'chinesemedicine'
#allowed_domains = ['http://yzs.satcm.gov.cn']
start_urls = ['https://www.ygdy8.net/html/gndy/china/index.html']
rules = (
Rule(LinkExtractor(allow=r'list_4_\d+\.html'),callback='parse_item', follow=True),
)
def parse_item(self, response):
detail_url_list = ['https://www.ygdy8.net' + el for el in response.xpath( "//table[@class='tbspan']/tr/td/b/a[2]/@href").extract()]
for url in detail_url_list:
yield scrapy.Request(url=url, callback=self.parse_detail)
def parse_detail(self,response):
print('url :'+response.xpath('//p/img[1]/@src').extract_first())
他与上一个类似,但区别在于使用 parse_detail(self,response)来解析详情页面,这个好处在于我们后面在增量查询时,减少建立连接数。
增量式爬取工作
- 在发送请求之前判断这个URL之前是不是爬取过
- 在解析内容之后判断该内容之前是否爬取过
- 在写入存储介质时判断内容是不是在该介质中
去重的方法
将爬取过程中产生的URL进行存储,存入到redis中的set中,当下次再爬取的时候,对在存储的URL中的set中进行判断,如果URL存在则不发起请求,否则就发起请求。
对爬取到的网站内容进行唯一的标识,然后将该唯一标识存储到redis的set中,当下次再爬取数据的时候,在进行持久化存储之前,要判断该数据的唯一标识在不在redis中的set中,如果在,则不在进行存储,否则就存储该内容。
一般而言,对url的去重是增量爬虫的关键,所以必须去重。爬虫爬取的是大规模的相同类型网站,对于每一个爬虫的结果还会汇总,所以对于内容解析去重并不一定要做,我们可以在汇总后写入存储介质时再去重,但对于汇总时对传输有要求时,还是解析内容去重。
Redis 安装
Window 下安装
下载地址:https://github.com/tporadowski/redis/releases。
Redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择,这里我们下载 Redis-x64-xxx.zip压缩包到 C 盘,解压后,将文件夹重新命名为 redis。
打开一个 cmd 窗口 使用 cd 命令切换目录到 C:\redis 运行:
redis-server.exe redis.windows.conf
如果想方便的话,可以把 redis 的路径加到系统的环境变量里,这样就省得再输路径了,后面的那个 redis.windows.conf 可以省略,如果省略,会启用默认的。
这时候另启一个 cmd 窗口,原来的不要关闭,不然就无法访问服务端了。
切换到 redis 目录下运行:
redis-cli.exe -h 127.0.0.1 -p 6379
这样就可以在shell中操作redis了。
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
我们把爬取过的url添加到Redis的set中,这样就可以通过判断url是否在set中判断url是否爬取过。
具体代码如下:
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
class ChinesemedicineSpider(CrawlSpider):
name = 'chinesemedicine'
start_urls = ['https://www.ygdy8.net/html/gndy/china/index.html']
rules = (
Rule(LinkExtractor(allow=r'list_4_\d+\.html'),callback='parse_item', follow=True),
)
def parse_item(self, response):
conn = Redis(host='127.0.0.1', port=6379)
detail_url_list = ['https://www.ygdy8.net' + el for el in response.xpath(
"//table[@class='tbspan']/tr/td/b/a[2]/@href").extract()]
for url in detail_url_list:
# ex == 1:set中没有存储url
print("from :"+ url)
ex = conn.sadd('movies_url',url)
if ex == 1:
yield scrapy.Request(url=url, callback=self.parse_detail)
else:
print('已爬取过')
def parse_detail(self,response):
#item = Scrapy2Item()
print('url :'+response.xpath('//p/img[1]/@src').extract_first())
#yield item
这样我们实现了简单的增量爬虫。