scrapy 爬虫框架总结
一、常用命令
创建scrapy项目
scrapy startproject weather
创建Spider
scrapy genspider Cqtianqi http://weather.cma.cn #蜘蛛名 顶级域名
运行蜘蛛
scrapy crawl CQtianqi
二、工作组件及流程
2.1 图解流程
2.2 组件介绍
七大组件、五个关键
Scrapy Engine
引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。
调度器(Scheduler)
调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎。
下载器(Downloader)
下载器负责获取页面数据并提供给引擎,而后提供给spider。
Spiders
Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。 每个spider负责处理一个特定(或一些)网站。 更多内容请看 Spiders 。
Item Pipeline
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)。 更多内容查看 Item Pipeline 。
下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response(也包括引擎传递给下载器的Request)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。更多内容请看 下载器中间件(Downloader Middleware) 。
一句话总结就是:处理下载请求部分
Spider中间件(Spider middlewares)
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。更多内容请看 Spider中间件(Middleware) 。
一句话总结就是:处理解析部分
2.3 数据流(Data flow)
Scrapy中的数据流由执行引擎控制,其过程如下:
- 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
- 引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
- 引擎向调度器请求下一个要爬取的URL。
- 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
- 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
- 引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
- Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
- 引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
- (从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。
三、详解各模块作用
spider
两个重要参数、一个方法、两种方式
✳️ allowed_domains:这是scrapy框架中规定爬取的url的范围,简单地说就是,我们只能在这个参数定义的url的范围下获取数据,一旦数据超出范围,那么我们的请求将会失效。
✴️ start_urls:这是scrapy框架起始访问的url,也即在最初向这个url的位置发起请求。
parse(self,response) 中,这个函数是当我们发起请求成功后的一个回调函数,如果不理解回调函数的不要紧,我们只需要知道,这个函数的触发时机是请求已经被正确处理了。于是我们可以拿到函数的传参:response的值,而这个response的值就是服务器给我们的响应。
1️⃣ response.xpath(xpath语句传入):这种方式是对response进行xpath解析,我们在括号内传入xpath语法对应的语句即可,要注意的是,普通的xpath解析,返回的是一个列表,但是在scrapy框架中的xpath解析,返回的是selector对象列表,针对selector对象列表,我们需要进一步的处理,才能真正拿到数据。
2️⃣ response.extract():这种处理,承接了上面的操作,当我们拿到了selector对象列表,通过再执行.extract()函数,即可把selector对象列表转成普通的列表,进而获取数据。
import scrapy
from weather.items import WeatherItem
class CqtianqiSpider(scrapy.Spider):
name = 'CQtianqi'
allowed_domains = ['weather.cma.cn']
start_urls = []
start_urls.append('http://weather.cma.cn/web/weather/57083.html/')
def parse(self, response):
n_url = response.xpath('//*[@id="cityPosition"]/div[5]/ul//a/@href').extract()
for i in n_url:
real_url = 'http://weather.cma.cn' + ''.join(i) + '/'
yield scrapy.Request(real_url, callback=self.parse)
date = response.xpath('//*[@id="dayList"]/div/div[1]/text()[2]').extract()
week = response.xpath('//*[@id="dayList"]/div/div[1]/text()[1]').extract()
item = WeatherItem()
item['date'] = date
item['week'] = week
yield item
parse()方法工作机制及应用
- 因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型;
- 如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息。
- scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取;
- 取尽第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline里处理;
- parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse)
- Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路)
- 取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
- 程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items。
items.py
作用:从非结构化的数据源中提取出结构化的数据
Scrapy支持以下类型的项目,通过 itemadapter 类库:
- dictionaries
- Item objects
- dataclass objects
- attrs objects
我们选用可自定义字段名称的Item对象来提取数据
定义Item非常简单,只需要继承scrapy.Item类,并将所有字段都定义为scrapy.Field类型即可
# items.py
from scrapy.item import Item, Field
class WeatherItem(Item):
date = Field()
week = Field()
Cqtianqi.py
......
#与之对应
item = WeatherItem()
item['date'] = date
item['week'] = week
yield item
pipelines.py
每一个Item Pipeline都有三个方法,process_item(self,item,spider)最为重要
(1)process_item(self, item, spider)
每一个Item Pipeline都会调用这个方法,用来处理Item,返回值为item或dict。
这个方法还可以抛出一个DropItem异常,这样将会不再继续调用接下来的Item Pipeline。
参数item(Item对象或者Dict) 是parse方法传来的。
参数spider(Spider对象) - 抓取这个Item的Spider。
(2)open_spider(self, spider)
这个方法将会在Spider打开时调用。
(3)close_spider(self, spider)
这个方法将会在Spider关闭时调用。
import os,json
import pymysql
import requests
from scrapy import Request
pathdir = os.getcwd()
class WeatherPipeline(object):
def process_item(self, item, spider):
base_dir = os.getcwd()
# 文件存在data目录下的weather.txt文件内
fiename = pathdir + '\\data\\weather.txt'
# 从内存以追加的方式打开文件,并写入对应的数据
with open(fiename, 'a', encoding='utf8') as f:
for i in range(7):
f.write('日期:' + item['date'][i] + '\n')
f.write('星期:' + item['week'][i] + '\n')
return item
还需在setting文件中配置
ITEM_PIPELINES = {
'weather.pipelines.WeatherPipeline': 300,
}
settings.py
各变量的作用
# -*- coding: utf-8 -*-
# Scrapy settings for demo1 project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
# http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
# http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'weather' #Scrapy项目的名字,这将用来构造默认 User-Agent,同时也用来log,当您使用 startproject 命令创建项目时其也被自动赋值。
SPIDER_MODULES = ['weather.spiders'] #Scrapy搜索spider的模块列表 默认: [xxx.spiders]
NEWSPIDER_MODULE = 'weather.spiders' #使用 genspider 命令创建新spider的模块。默认: 'xxx.spiders'
#爬取的默认User-Agent,除非被覆盖,需要改,模拟正常用户访问
#USER_AGENT = 'demo1 (+http://www.yourdomain.com)'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
#如果启用,Scrapy将会采用 robots.txt策略,建议不遵循
ROBOTSTXT_OBEY = False
#Scrapy downloader 并发请求(concurrent requests)的最大值,默认: 16
CONCURRENT_REQUESTS = 32
#为同一网站的请求配置延迟(默认值:0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3 下载器在下载同一个网站下一个页面前需要等待的时间,该选项可以用来限制爬取速度,减轻服务器压力。同时也支持小数:0.25 以秒为单位
#下载延迟设置只有一个有效
#CONCURRENT_REQUESTS_PER_DOMAIN = 16 对单个网站进行并发请求的最大值。
#CONCURRENT_REQUESTS_PER_IP = 16 对单个IP进行并发请求的最大值。如果非0,则忽略 CONCURRENT_REQUESTS_PER_DOMAIN 设定,使用该设定。 也就是说,并发限制将针对IP,而不是网站。该设定也影响 DOWNLOAD_DELAY: 如果 CONCURRENT_REQUESTS_PER_IP 非0,下载延迟应用在IP而不是网站上。
#禁用Cookie(默认情况下启用)
#COOKIES_ENABLED = False
#禁用Telnet控制台(默认启用)
#TELNETCONSOLE_ENABLED = False
#覆盖默认请求标头:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
#启用或禁用蜘蛛中间件
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'demo1.middlewares.Demo1SpiderMiddleware': 543,
#}
#启用或禁用下载器中间件
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'demo1.middlewares.MyCustomDownloaderMiddleware': 543,
#}
#启用或禁用扩展程序
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
#配置项目管道
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'weather.pipelines.WeatherPipeline': 300,
}
#启用和配置AutoThrottle扩展(默认情况下禁用)
# See http://doc.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
#初始下载延迟
#AUTOTHROTTLE_START_DELAY = 5
#在高延迟的情况下设置的最大下载延迟
#AUTOTHROTTLE_MAX_DELAY = 60
#Scrapy请求的平均数量应该并行发送每个远程服务器
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
#启用显示所收到的每个响应的调节统计信息:
#AUTOTHROTTLE_DEBUG = False
#启用和配置HTTP缓存(默认情况下禁用)
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
中文文档
四、其他问题
域名和url的区别
域名
域名是由一串用“.”分隔的名字组成的Internet上某一台计算机或计算机组的名称。
作用:用来将主机名和域名转换为IP地址的工作。
URL
URL以字符串的抽象形式来描述一个资源在万维网上的地址。一个URL唯一标识一个Web资源,通过与之对应的URL即可获得该资源。可以用来定位网页,多媒体文件等
常用的URL格式如下:协议类型://服务器地址[:端口号]/路径/文件名[参数=值]
URL和域名的区别
域名,Domain Name,通常指一个网址的顶级域名。
URL,website address,网页或网站的地址。
URL中包含了网站的域名.
比如一个网址:www.cnblogs.com/gopark/p/8430916.html
其中cnblogs.com是域名,cnblogs是网站名字,com是域名后缀;www.cnblogs.com代表一个二级域名,通常www被用来用为首页标识;
https://www.cnblogs.com/gopark/p/8430916.html,这个则是一个完整的网站首页URL地址。https://,这是一个协议,是网站在网上传输的协议。
scrapy如何保证不重复请求同一个url,如何改变
scrapy默认去重、一般无需修改
(1)跟进的url不重复时
当spider返回需要跟进的url时,如果没有指定dont_filter的值,那么默认为False,即自动过滤重复请求,当需要重复请求同一个url时,需要将dont_filter的值设为True
# 默认过滤重复请求
scrapy.Request(url=addr, callback=self.self.parse)
# 设置dont_filter = True不过滤
scrapy.Request(url=addr, callback=self.new_parse, dont_filter=True)
(2)
若在 scrapy 中,不过滤任何 request 请求,可以自定义如下文件
from scrapy.dupefilter import RFPDupeFilter
class CloseDupefilter(RFPDupeFilter):
def request_seen(self, request):
return False
然后在settings.py中添加如下代码
DUPEFILTER_CLASS = 'scraper.duplicate_filter.CustomFilter'
增量爬取时如需重复爬取可参考
增量去重爬取
回调函数
字面上的理解,回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调。
x-path语言
用途:提取网页数据
通常返回选择器对象
表达式 | 描述 |
/ | 根节点选取或下级 |
// | 任意节点,不考虑位置 |
. | 当前节点 |
… | 当前节点的父节点 |
@ | 选取属性 |
* | 匹配任何节点 |
[] | 根据节点筛选 |
contains(@属性,“包含的内容”) | 模糊查询 |
text() | 文本内容 |
3.案例
取得class为classify的h3标签下a标签内的内容
//h3[@class="classify"]/a/text()
利用模糊查询查找class类名中包含classify_list的所有div标签下的span下的a标签的文本内容
//div[contains(@class,"classify_list")]/span/a/text()
MutableMapping
映射类型
什么是可散列对象
如果一个对象是可散列的,那么在这个对象的生命周期中,他的散列值是不会变的(它需要实现__hash__()方法)。它可以与其他对象作比较(还需实现__eq__()方法)。如果一个可散列对象与另一个可散列对象是相等的,那么他们的散列值hash value一定是相等的。
原子不可变数据类型(str,bytes和数值类型)都是hashable类型,frozenset也是hashable的,因为根据其定义,frozenset里只可容纳可散列类型。元组也是hashable的,但只有当元组包含的所有元素都是hashable类型的情况下它才是可散列的。
简单来说,如果一个对象是可散列的数据类型的话,那它应是不可变的。
test = (1,2,(3,4))
hash(test)
-2725224101759650258
test1 = (1,2,[3,4])
hash(test1)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: unhashable type: 'list'
list等可变对象是不可散列的,因为随着数据的改变他们的哈希值会变化导致进入错误的哈希表。
一般用户自定义的类型的对象都是可散列的,散列值就是它们的id()函数的返回值,所以所有这些对象在比较的时候都是不想等的。如果一个对象实现了__eq__()方法,并且在方法中用到了这个对象的内部状态的话,那么只有当所有这些内部状态都是不可变的情况下,这个对象才是可散列的。