简介
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。我们只需要少量代码,就能够快速的抓取。
Scrapy使用了Twisted[‘twisted’]异步网络框架,可以加快我们的下载速度。
Scrapy的工作流程
模块 | 功能 | 实现方式 |
---|---|---|
Scrapy Engine(引擎) | 总指挥:负责数据和信号的在不同模块间传递 | scrapy框架实现 |
Scheduler (调度器) | 一个队列,存放引擎发过来的request请求 | scrapy框架实现 |
Downloader (下载器) | 下载引擎发送来的requests请求,并返回引擎 | scrapy框架实现 |
Spider (爬虫) | 处理引擎发来的response,提取数据,提取url,并交给引擎 | 需要手写 |
Item Pipeline (管道) | 处理引擎传过来的数据,比如存储 | 需要手写 |
Downloader Middlewares (下载器中间件) | 可以自定义的下载扩展中间件,比如设置代理 | 一般不用手写 |
Spider Middlewares (爬虫中间件) | 可以自定义request请求和进行response过滤 | 一般不用手写 |
Scrapy创建项目
# 安装scrapy
pip install Scrapy
# 创建项目
scrapy startproject projectname
# 创建爬虫
scrapy genspider spidername spider域名
# 提取数据,完善spider。
# 保存数据,pipeline中保存数据。
# 启动爬虫
scrapy crawl spidername
Scrapy项目目录结构
Scrapy编写爬虫
爬虫文件
创建爬虫之后,会在项目中spiders目录下生成爬虫名的py文件。我们要对这个文件进行完善。编写数据爬取方式。
itcast.py
import scrapy
class ItcastSpider(scrapy.Spider):
name = 'itcast' # 定义的爬虫名
allowed_domains = ['itcast.cn'] # 允许爬取的域名范围
start_urls = ['http://www.itcast.cn/channel/teacher.shtml'] # 开始爬取的url
def parse(self, response):
# 数据解析方法。方法名不能修改。
li_list = response.xpath("//div[@class='tea_con']//li")
for li in li_list:
item = {}
item["name"] = li.xpath(".//h3/text()").extract_first()
item["title"] = li.xpath(".//h4/text()").extract()[0]
# extract_first() == extract()[0]
yield item
从选择其中提取字符串:
- extract() 返回一个包含有字符串数据的列表。
- extract_first() 返回列表的第一个字符串。同extract()[0]
注意:
- spider中的parse方法名不能修改。
- 需要爬去的URL地址必须要属于allow_domain下的链接。start_urls 首次爬取的时候,可以不满足此条件。
- response.xpath()返回的是一个包含selector对象的列表。
- scrapy中的response没有content属性,使用body替代。response.body是二进制信息,使用时需要decode()转码。
spider的数据传到pipline使用yield。让整个函数变成一个生成器,减少内存占用。
爬虫构造请求
很多时候,爬虫需要从页面中提取出 URL,并爬取该URL中的内容。这时候就需要构造请求。
class ItcastSpider(scrapy.Spider):
name = 'itcast' # 定义的爬虫名
allowed_domains = ['itcast.cn'] # 允许爬取的域名范围
start_urls = ['http://www.itcast.cn/channel/teacher.shtml'] # 开始爬取的url
def parse(self, response):
next_url = response.xpath("").extract_first() # 获取到二级URL
# 构造请求
yield scrapy.Request(next_url, callback=self.parse2)
# 解析函数间传递数据。parse 传递数据到parse2
# item = {}
# .. 省略item中数据的获取过程。
# yield scrapy.Request(next_url, callback=self.parse2, meta={"item"=item})
def parse2(self, response):
# 对二级URL所做处理
pass
scrapy.Request 能构建一个requests,同时制定提取数据的callback函数。
scrapy.Request(url [, callback, method, headers, body, cookies, meta, dont_filter=False])
- meta: 实现在不同的解析函数中传递数据,meta默认会携带部分信息,比如下载延迟,请求深度等。
class ItcastSpider(scrapy.Spider):
name = 'itcast' # 定义的爬虫名
allowed_domains = ['itcast.cn'] # 允许爬取的域名范围
start_urls = ['http://www.itcast.cn/channel/teacher.shtml'] # 开始爬取的url
def parse(self, response):
next_url = response.xpath("").extract_first() # 获取到二级URL
# 构造请求
# 解析函数间传递数据。parse 传递数据到parse2
item = {}
.. 省略item中数据的获取过程。
yield scrapy.Request(next_url, callback=self.parse2, meta={"item"=item})
def parse2(self, response):
# 对二级URL所做处理
item = response.meta["item"]
pass
- dont_filter: 让scrapy的去重不会过滤当前url,scrapy默认有url去重的功能,对需要重复请求的url有重要用途。
pipeline
class MyspiderPipeline:
def open_spider(self, spider):
pass
def process_item(self, item, spider):
# 在这个方法中编写数据存储的方法。
return item
def close_spider(self, spider):
pass
完成pipeline代码后,需要在settings中设置开启pipeline
ITEM_PIPELINES = {
'myspider.pipelines.MyspiderPipeline': 300,
# 'myspider.pipelines.MyspiderPipeline' pipeline的位置
# 300 权重。当有多个pipeline时,通过权重大小,决定执行顺序。
}
从settings中的pipeline设置可以看出,pipeline可以有多个。主要为了两个目的:
- 可能会有多个spider,不同的pipeline处理不同的item内容。
- 一个spider的内容可能要做不同的操作,比如存入不同的数据库。
注意:
- pipeline的权重越小,优先级越高。
- pipeline中process_item方法名不能修改。
open_spider方法,不是默认生成的。这个方法从名字可以看出,是在开始爬虫之前调用的方法(既此方法运行在爬虫文件之前。)这个方法在整个爬虫过程中,只会执行一次。通过此方法,可以对spider对象预先添加属性信息,还可以进行连接数据库实例化,打开本地文件等操作。
closer_spider方法,在爬虫关闭的时候执行,仅执行一次。可以进行数据库关闭连接,关闭本地文件等结束操作。
items
为要提取的数据构建数据模型。
items.py
import scrapy
class MyspiderItem(scrapy.Item):
name = scrapy.Field()
.. 省略更多
在爬虫文件中,如果使用了items,则只能构建模型中定义的字段。如果模型中没有,会报错。
如果爬虫文件中成功使用了items获取数据,在pipeline.py文件中,item则为对应模型,可以通过下面方法进行判断。
from myspider.items import MyspiderItem
class MyspiderPipeline:
def process_item(self, item, spider):
if isinstance(item, MyspiderItem):
..
# 通过dict(item) 可以强转为dict字典类型。
return item
Log
在settings中,可以设置在终端输出日志的Log等级。通过设置这个参数,可以禁止scrapy的加载和运行信息在终端的输出。
LOG_LEVEL = "WARNING"
在可能出现异常的地方,加log输出。如爬虫文件,pipelines.py文件。
import logging
logger = logging.getLogger(__name__) # 这样处理之后,打印出的日志可以看到所在文件名
# 可能出现异常的地方
logger.warning("loginfo")
日志不在终端打印,保存本地的设置方法。
在settings中添加
LOG_LEVEL = "WARNING"
LOG_FILE = "./log.log"
settings
settings中的配置都有详细的文档教学,可以自行在使用时进行查看。
BOT_NAME = 'myspider'
SPIDER_MODULES = ['myspider.spiders']
NEWSPIDER_MODULE = 'myspider.spiders'
LOG_LEVEL = "WARNING"
LOG_FILE = "./log.log"
# 设置user_agent。
USER_AGENT = ''
# Obey robots.txt rules 这个参数决定了爬虫是否遵循robots协议。
ROBOTSTXT_OBEY = True
# 设置最大并发请求数 (default: 16)
CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# 是否使用cookies,scrapy在请求二级url时,可以自动携带上级cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# 重写headers,注意user_agent,cookies 信息不能放入请求头,需要单独处理:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'myspider.middlewares.MyspiderSpiderMiddleware': 543,
#}
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'myspider.middlewares.MyspiderDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'myspider.pipelines.MyspiderPipeline': 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.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'
Scrapy CrawlSpider 使用
通常情况下,在爬取页面中的URL时,我们是根据xpath一个个获取,然后构造。
使用CrawlSpider可以让这个过程变得简单,让scrapy框架根据我们设定的规则,自动获取所需的URL。
# 创建爬虫命令
scrapy genspider -t crawl spidername spider域名
生成的爬虫文件
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class ChufaSpider(CrawlSpider):
name = 'chufa'
allowed_domains = ['cicr.gov.cn']
start_urls = ['http://cicr.gov.cn/']
# 定义提取URL地址规则
# Rule是一个对象,在rules中实例化
# rules是一个元祖,可以实例化多个Rule
rules = (
# allow URL的正则。不需要主域名,会自动补充。
# LinkExtractor 连接提取器,提取URL地址
# callback 提取出来的URL地址的response会交给callback处理,根据业务判断是否需要此参数。
# follow 当前URL地址的响应是否重新经过rules来提取URL地址
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
def parse_item(self, response):
item = {}
#item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
#item['name'] = response.xpath('//div[@id="name"]').get()
#item['description'] = response.xpath('//div[@id="description"]').get()
return item
注意:
- CrawlSpider爬虫文件中,不能定义parse方法,parse有特殊功能。
- 如果多个Rule都满足某一个URL,会从rules中选择第一个满足的进行操作。
LinkExtractor更多常见参数:
- deny:满足括号中正则的URL一定不提取,优先级高于allow。
- allow_domains:会被提取的连接的domains。
- deny_domains:一定不会被提取连接的domains。
- restrict_xpaths:使用xpath表达式过滤连接。
spider.Rule更多常见参数:
- process_links:指定某个函数。在获取到连接列表时将会调用该函数,主要用来过滤URL。
- process_request:指定某个函数。在该Rule提取到的每个requests时都会调用该函数,用来过滤requests。
Downloader Middlewares 下载中间件
当需要对请求和响应做自定义处理时,使用下载中间件。
Downloader Middlewares 默认的方法:
- process_request(self, request, spider)
当每个request通过下载中间件时,该方法被调用。 - process_response(self, request,response,spider)
当下载器完成http请求,传递响应给引擎的时候调用。
class RandomUserAgentMiddleware:
def process_request(self, request, spider):
# 提前在settings中定义USER_AGENTS_LIST列表。
ua = random.choice(spider.settings.get("USER_AGENTS_LIST"))
request.headers["User-Agent"] = ua
request.meta["proxy"] = "协议+ip+端口" # 添加代理
def process_response(self, request, response, spider):
print(request.headers["User-Agent"]) # 查看每次使用的ua是否不同。
return response
中间件定义之后,需要在settings中注册,同pipeline。
Scrapy 模拟登陆
在不使用scrapy,使用requests做爬虫时,模拟登陆有三种方法:
- 直接携带cookies请求页面。可以放到header中,也可以在requests中给到cookies参数。
- 找接口发送POST请求,存储cookies。
- selenium.找到对应的input标签,输入信息点击登陆。
使用scarpy模拟登陆,也是同样的三种方法:
- 直接携带cookies。不能放到请求头中,必须给到cookie
- 找接口发送POST请求,scrapy默认会自动存储cookies(在settings中可以设置)。
- selenium把登录后的cookies保存到本地,scrapy发送请求前读取本地cookies。
在开始使用scrapy模拟登陆之前,先要理解start_url的访问流程。首先,上面提到过,start_url在访问时,不会被allowed_domains规则所限制(这个限制是通过OffsiteMiddleware中间件实现的)。这是因为,我们在parse中构造的请求,是使用的scrapy.Request,而start_url的请求是使用的spider.start_requests 方法。通过源码可以看到具体实现内容。
理解了start_url的请求,就可以推测出,如果start_url是需要登录才能访问的,可以通过改写spider.start_requests 方法来实现登录后访问。
import scrapy
class ItcastSpider(scrapy.Spider):
name = '' # 定义的爬虫名
allowed_domains = [''] # 允许爬取的域名范围
start_urls = [''] # 开始爬取的url
def start_requests(self):
cookies = ""
# cookies转化为字典
cookies = {i.split("=")[0]:i.split("=")[1] for i in cookies.split("; ")}
yield scrapy.Request(
self.satrt_urls[0],
callback=self.parse,
cookies=cookies
)
def parse(self, response):
url = ''
# 默认情况下,scrapy在下次url请求时,可以自动携带cookies,不需要再次指定。
yield scrapy.Request(
url,
callback=self.parse_second
)
def parse_second(self, response):
pass
小技巧:可以在settings中设置 COOKIES_DEBUG=TRUE 参数,这样我们就可以在终端输出中看到cookie的传递,和cookie信息了。
Scrapy 模拟POST请求
使用scrapy.FormRequest方法。
class ItcastSpider(scrapy.Spider):
name = '' # 定义的爬虫名
allowed_domains = [''] # 允许爬取的域名范围
start_urls = [''] # 开始爬取的url
def parse(self, response):
post_data = {}
.. 省略构造post_data
yield scrapy.FormRequest(
"url", # post请求路径
formdata=post_data,
callback=self.after_login
)
def after_login(self, response):
pass
上面这种方法,需要人为找到POST请求地址。scrapy提供了自动获取Form表单提交地址路径的方法。
class ItcastSpider(scrapy.Spider):
name = '' # 定义的爬虫名
allowed_domains = [''] # 允许爬取的域名范围
start_urls = [''] # 开始爬取的url
def parse(self, response):
yield scrapy.FormRequest.from_response(
response, # 自动的从response中寻找Form表单提交地址
formdata={"login":"..","password":".."}, # 需要输入表单的input名和要输入的值。scrapy自动填充后提交。
callback=self.after_login
)
def after_login(self, response):
pass
Scrapy shell
可以在终端,对Scrapy提供的参数进行使用和测试。包括测试xpath的结果、查看response包含的信息等等。
scrapy shell <url>
Scrapy Redis
scrapy_redis在scrapy的基础上实现了更多,更强大的功能。具体体现在:request去重,爬虫持久化,轻松实现分布式爬虫。