需求
- 给定爬虫起始地址,爬行路径,获得目标页的指定内容
- 约定路径 起点首页 --> (历史)频道 --> 小说info页–> 章节详情起始页 --> 作者中心 --> 大神之光
- 路径中的每个页面html结构都不同
分析
- 通用爬虫,写规则,用链接抽取器
- 为便于演示,只展示起点与终点页指定内容,中间路径以爬虫引擎debug信息展示
准备
- scrapy 1.6
- SelectorGadget 类似消消乐式浏览器扩展工具,点击消除颜色块,生成css或xapth表达式
代码
# -*- coding: utf-8 -*-
import scrapy
from scrapy.spiders.crawl import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
# item填充器
from scrapy.loader import ItemLoader
from scrapy.loader.processors import Join, MapCompose, TakeFirst
"""
爬虫深度测试
关注点在deep_max
"""
def q(url):
print('提取的链接%s' % url)
return url
def l(links):
for link in links:
print(link)
return links
class QdSpider(CrawlSpider):
name = 'qd'
allowed_domains = ['qidian.com']
start_urls = ['https://www.qidian.com/']
rules = (
Rule(
LinkExtractor(
# 允许爬虫深入的页面,每个响应页面只提取一个页面链接
# 路径 历史频道 -> 选择特定的info ->章节->作者->大神之光 五个页面,至少5个深度
allow=(r'(lishi)$', r'(info)\/(\d+)$', r'(chapter)/',
r'(authorIndex)', r'author/light/(\d+)$'),
# 规则不通过,则会中断后续链接提取
# deny=('chapter/(.*?)$',),
# 从爬虫响应页面指定区域抽取,为便于观察每个页面只爬取一个链接
restrict_css=(
# 首页响应提取历史链接
'.even:nth-child(8)',
# 历史频道响应页面info链接
'.sort-list h4 a',
# info页面提取免费试读(章节)链接
'#readBtn ',
),
# 用xapth提取//协议开头的a 链接,restrict_css 不能提取诸如href="//me.qidian.com/..."这样的链接
restrict_xpaths=(
# 章节页面提取作者家链接
'//h2//a',
# 提取作者页的大神之光链接
'//div[@class="author-sidebar-more"]/a[@class="blue"]'
),
# process_value=q, 回调参数是提取的url字符串
),
# 决定爬虫是否在符合规则的页面接着爬
follow=True,
# 可调用对象
callback='parse_light',
# 回调参数是个Link对象
# process_links=l
),
)
def parse_start_url(self, response):
# 起始地址解析,没有回调的scrapy蜘蛛(crawled)响应交给了parse方法
yield {
'nav_home': response.css('.nav-li a::text').extract(),
'info': response.css('#notice h3 a::text').get()
}
def parse_light(self, response):
# 每次 Crawled (200)地址都会被规则过一遍,通过者入此
# self.log('[to...大神之光]%s...' % response.url)
l_light = response.css('td:nth-child(1) .dark::text').extract()
if len(l_light) > 0:
return {
'light': l_light
}
执行
scrapy crawl qd
效果
2019-05-29 19:50:04 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.qidian.com/> (referer: None)
2019-05-29 19:50:04 [scrapy.core.scraper] DEBUG: Scraped from <200 https://www.qidian.com/>
{'nav_home': ['全部作品', '排行', '完本', '免费', '作家专区', '客户端'], 'info': '党员作家班结业'}
2019-05-29 19:50:04 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.qidian.com/lishi> (referer: https://www.qidian.com/)
2019-05-29 19:50:04 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://book.qidian.com/info/1012237441> (referer: https://www.qidian.com/)
2019-05-29 19:50:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://book.qidian.com/info/1011705052> (referer: https://www.qidian.com/lishi)
2019-05-29 19:50:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://read.qidian.com/chapter/M-D2AI2m8IBdi5-cWpsVtA2/eSlFKP1Chzg1> (referer: https://book.qidian.com/info/1012237441)
2019-05-29 19:50:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://read.qidian.com/chapter/rD2MpxrmEaLv7_WH3wfEdQ2/eSlFKP1Chzg1> (referer: https://book.qidian.com/info/1011705052)
2019-05-29 19:50:05 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET https://my.qidian.com/author/3228548> from <GET https://me.qidian.com/authorIndex.aspx?id=3228548>
2019-05-29 19:50:05 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET https://my.qidian.com/author/2438700> from <GET https://me.qidian.com/authorIndex.aspx?id=2438700>
2019-05-29 19:50:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://my.qidian.com/author/3228548> (referer: https://read.qidian.com/chapter/M-D2AI2m8IBdi5-cWpsVtA2/eSlFKP1Chzg1)
2019-05-29 19:50:06 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://my.qidian.com/author/2438700> (referer: https://read.qidian.com/chapter/rD2MpxrmEaLv7_WH3wfEdQ2/eSlFKP1Chzg1)
2019-05-29 19:50:06 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://my.qidian.com/author/light/3228548> (referer: https://my.qidian.com/author/3228548)
2019-05-29 19:50:06 [scrapy.core.scraper] DEBUG: Scraped from <200 https://my.qidian.com/author/light/3228548>
{'light': ['秋風夜影', 'milanmack', '谜月', '猿人阿兴', '猿人阿兴']}
2019-05-29 19:50:06 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://my.qidian.com/author/light/2438700> (referer: https://my.qidian.com/author/2438700)
2019-05-29 19:50:06 [scrapy.core.scraper] DEBUG: Scraped from <200 https://my.qidian.com/author/light/2438700>
{'light': ['秋風夜影', 'milanmack', '谜月', '猿人阿兴', '猿人阿兴']}
2019-05-29 19:50:06 [scrapy.core.engine] INFO: Closing spider (finished)
scrapy 通用爬虫
- 流程
- 一个不停从Scheduler中获取请求,发起,下载,处理,响应的过程
request --> download --> response
Spider <-- spider Middlewares --> Engine <-- Downloader Middlewares --> Downloader
Engine <---> Engine Scheduler
Engine -- processed items --> item Pipelines
- 概念
- Spider 页面解析回调 接收response对象,返回 items,requests,None,dict或baseRequest对象
- 源请求地址不要在item填充阶段修改,所有对请求的修改放在中间件内进行
- windows路径在linux下表示,要加盘符 如
'D:/code-base/python/crawl/bigPhoto'
- scrapy.requset 对象发起的请求,未指定回调函数,则会进入蜘蛛的parse默认回调
- Rule
- Rule 对爬虫获取的响应页面进行链接分析
- 若没有指定规则处理器(规则回调参数),则默认交给parse处理
- 在debug中表现出来的就是对referer页的进行规则分析,若refer为None,则认为对downloader的start_url页面进行链接分析
- follow布尔值 决定是否对符合规则的页面进一步爬取,即跟进链接分析
- 通常规则参数没有回调,则为true, 默认为false
- follow本质是否允许爬虫对规则回调的响应页面进行继续爬取
- 规则回调生效条件
- 提取的链接符合规则要求,包括链接处理,过滤
- 需要爬虫爬过后的响应response,且该响应没有被禁止爬取,即follow为True
- 使用规则的页面,是爬虫提请请求,由引擎调度下载器,下载器下完后返回的响应页面
response.follow
会将response页作为referer页
- 核心
- 规则使用对象 对爬虫抓取页面 response响应 的分析处理
- 是否对符合规则的页面进行链接分析(爬取),表现为Rule的follow参数,默认为false
- 应用规则的爬虫响应页从哪来?
- 起点start_url
- 规则参数follow为True,所在的提取器得到的合规链接响应,(决定爬虫是否接着爬虫)
- 表现: follow为True,所得到的链接会被所有规则作为响应源,作新一轮规则分析
- 流程
- 爬虫响应 --> 逐一应用规则 – 对
follow=True
所产生新的response->进行新一轮规则应用 - 爬取 --> 响应 --> 规则分析 --> 处理器分发 --> 未指定则交给parse默认方法
- 关键问题
- 从哪儿爬? – 从爬虫响应页
- 要不要继续爬? – follow参数
- 最大deep_max问题 从起始页开始(不包含),起始->A页->B页 至少深度为2
- 常见问题
- 提取的链接有重定向,或重新拼接(通过js)成其它目标链接的痕迹,导致直接访问无响应,不能进行下一步follow处理
- scrapy的css选择器不支持
//
开发的链接提取,建议链接提取器使用a标签