课程资源: 【小琦资源】Python全系列之爬虫scrapy框架及案例
MENU
- Scrapy框架介绍
- Scrapy 框架安装和使用方法(Windows 操作系统)
- Scrapy 项目文件构成
- Scrapy Shell
- Setting模块
- Pipeline 模块
- Middleares 中间件模块
- requests请求翻页
- Scrapy中的CrawlSpider
- Scrapy模拟登录之携带cookie登录
- Scrapy模拟登录之发送post请求
- scrapy_redis 相关
- 动态网站数据抓取
- 列表生成式:
- extract() 和 extract_first() 的区别:
- yield 生成器
- selector 选择器
- 实战例子01 ---- 爬取苏宁易购图书
- 实战例子02 ---- 京东爬虫
- 实战例子03 ---- 当当爬虫
- 实战例子04 ---- 亚马逊爬虫
Scrapy框架介绍
- scrapy框架爬虫流程
- 特点
- 通过引擎调度几大模块减少模块间的依赖
- 可以通过 Middlewares 中间件进行一些处理
- 爬取流程
scheduler 存放初始url --> 引擎 --> downloader 下载页面内容 --> 引擎 --> spiders 提取数据/提取下一个url --> 引擎 --> item pipeline 对数据操作(存取,打印)
Scrapy 框架安装和使用方法(Windows 操作系统)
- 安装(anaconda)
- 使用anaconda直接 conda install scrapy
- 过后会停留在Proceed <[y]/n>? 这一行
- 输入 y 确认 继续安装
- 待安装完成后,输入 scrapy
- 若能显示出scrapy的一些命令则为安装成功
- 安装(python 3.7)
- scrapy框架的安装需要许多的依赖库,其中 Twisted 用 pip 方法安装会不成功
- 因此,先去下载 Twisted 的离线安装包,使用CTRL + F 查找Twisted,再下载相应的 whl 文件
- win32 即 32 位系统,amd64 即 64 位系统
- 在 cmd 终端 通过 cd 命令,打开下载的文件所在的文件夹
- 输入pip install 文件名.whl安装
- 安装完成后通过 pip install scrapy 安装 scrapy 框架
- 使用
- 导入:
import scrapy
- 命令:
- 测试 xpath/css 表达式:
response.xpath(“表达式”) [ [0], .extract(), .extract_first() ]
response.css(‘表达式’) [ [0], .extract(), .extract_first() ] - 创建:
----------常用命令---------- -----作用----- scrapy | 弹出scrapy命令列表 scrapy startproject 项目名 | 创建一个项目 scrapy genspider 爬虫名 url | 创建一个爬虫 scrapy genspider -t 模板 name url | 一般用的有basic(默认)和crawl cd 项目名 | 进入项目文件夹(Windows命令) cd .. | 退出项目(Windows命令) scrapy check | 检查代码是否存在错误,并且指出错误 scrapy list | 返回项目里所有的爬虫名称 scrapy edit 爬虫名 | 在命令行进行编辑(不方便,一般不用) scrapy settings | 查看设置 scrapy settings -h 配置名 | 获取配置信息 scrapy runspider name.py | 运行单独一个爬虫文件 scrapy shell url | 进入交互终端,用于调试爬虫 scrapy fetch url | 请求这个网址并且返回内容 scrapy view url | 直接把收集的内容在浏览器中打开 scrapy version | 查看版本信息 scrapy crawl 爬虫名 -o file_name | 运行爬虫,并输出特定文件(文件可不输出) scrapy runspider 爬虫名.py | 运行爬虫文件 上述命令 --nolog | 不显示日志 上述命令 --headers | 返回响应头信息 上述命令 --no-redirect | 网址重定向
- 测试 xpath/css 表达式:
- 导入:
Scrapy 项目文件构成
- 当创建好一个项目后,会生成几个项目文件。
- 具体介绍如下:
|-- spider. py ---- 爬虫文件夹 |---- _init_. py ---- 默认的爬虫代码文件 |---- itcast. py ---- 具体爬虫代码文件 |-- _init_. py ---- 爬虫项目的初始化文件,用来对项目做初始化工作。 |-- items. py ---- 爬虫项目的数据容器文件,用来定义要获取的数据。 |-- pipelines. py ---- 爬虫项目的管道文件,用来对items中的数据进行进一步的加工处理 |-- middlewares.py ---- 爬虫中间件文件,处理request和reponse等相关配置 |-- setting. py ---- 爬虫项目的设置文件,包含了爬虫项目的设置信息 |-- scrapy. cfg ---- 爬虫项目的配置文件,定义了项目的配置文件路径、部署相关信息等内容
Scrapy Shell
命令:scrapy shell url
scrapy shell 是一个交互式终端
当我们输入命令后,会刷新出一系列的运行信息,同时也会展示出当前哪些对象时可以使用的
找到 Available Scrapy objects ,会看到下面有一系列的选项
- 作用:
- 不清楚response有哪些属性时,可以输入 response.(有个小点) ,就会显示出 Response 的所有属性
- 调试 xpath 表达式
- 例子:
response.url :当前相应的url地址
response.request.url:当前响应对应的请求的url地址
response.headers:响应头
response.body:响应体,也就是html代码,默认是 byte 类型
response.requests.headers:当前响应的请求头(默认User-Agent更改)
Setting模块
- setting 文件解析
- 存在意义:
1.存放一些公共的变量(数据库的地址,账号密码等)
2.集中度高,方便修改- 一般使用全大写字母命名变量名:SQL_HOST = ‘192.168.0.1’
Scrapy settings for mySpider project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://doc.scrapy.org/en/latest/topics/settings.html
# https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'mySpider' :项目名
SPIDER_MODULES = ['mySpider.spiders'] :爬虫文件位置
NEWSPIDER_MODULE = 'mySpider.spiders' :新建爬虫文件的位置
LOG_LEVEL = 'WARNING' :debug显示信息,WARNING及WARNING以上
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'mySpider (+http://www.yourdomain.com)' :生成标识
# Obey robots.txt rules
ROBOTSTXT_OBEY = True :默认为True:遵守Robot协议 | False:不遵守
# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 32 :设置最大编码请求 越大越快乐,但是也容易被发现,默认即可
# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 3 :下载延迟,请求前等3秒钟,可以让爬虫爬取速度变慢
# The download delay setting will honor only one of:
CONCURRENT_REQUESTS_PER_DOMAIN = 16 :每个域名的最大并发数
CONCURRENT_REQUESTS_PER_IP = 16 :每个IP地址最大并发数
# Disable cookies (enabled by default)
COOKIES_ENABLED = False :Cookie是否开启,在请求下一个url地址的时候是否带上cookie
# Disable Telnet Console (enabled by default)
TELNETCONSOLE_ENABLED = False :一个插件
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
} :默认请求头,user Agent放这里是没有用的,上面有专门存user Agent的地方
# Enable or disable spider middlewares
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES = {
'mySpider.middlewares.MyspiderSpiderMiddleware': 543,
} :爬虫中间件
# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
'mySpider.middlewares.MyspiderDownloaderMiddleware': 543,
} :下载中间件
# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
EXTENSIONS = {
'scrapy.extensions.telnet.TelnetConsole': None,
} :
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = { #pipeline 优先级 数值表示距离引擎距离
'mySpider.pipelines.MyspiderPipeline': 300,
} :位置,权重
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://doc.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://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
HTTPCACHE_ENABLED = True :HTTP缓存
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache'
HTTPCACHE_IGNORE_HTTP_CODES = []
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
-1. 每个模块的 See 语句后面的 url 都是各个模块对应的文档,可以直接去文档查看
-2. setting 文件中大部分模块都不常使用,对于大部分模块了解其作用即可
-3. 初始 setting 文件中的模块默认为关闭状态(被注释)
-4. 全局变量的创建直接在 setting 文件中赋值即可,使用则可通过 from 项目名.settings import XXX
-5. 全局变量也可以通过 self.settings["变量名"] 获取,通过 self.settings.get("变量名","传入的值") 赋值
-6. pipelines 文件就用 spider.settings.get("变量名")
- logging格式
- scrapy
- settings中设置 LOG_LEVEL = “WARNING”
- settings中设置 LOG_FILE = “./a.log” #设置日志保存的位置,设置会后终端不会显示日志内容
- import logging , 实例化 logger 的方式在任何文件中使用logger输出内容
- 普通项目中
- import logging
- logging.basicConfig(…) #设置日志输出的样式,格式
- 实例化一个 ‘logger = logging.getLogger(name)’
- 在任何 py 文件中调用 logger 即可
Pipeline 模块
- 使用方法
open_spider(self, spider) / 开启爬虫时执行,仅执行一次(连接数据库等需要预加载的东西)
close_spider(self, spider) / 关闭爬虫时执行,仅执行一次
process_item(self, item, spider) / 若函数内没有 return ,另一个权重较低的pipeline就不会获取到该 item
Middleares 中间件模块
-
使用方法:编写一个Downloader Middlewares和我们编写一个pipeline一样,定义一个类,然后再setting中开启
Downloader Middlewares默认的方法: process——request(self, request, spider): 当每个request通过下载中间件时,该方法被调用 process_respinse(self, request, response, spider): 当下载器完成http请求,传递响应给引擎的时候调用
-
settings 文件中需要开启 DOWNLOADER_MIDDLEWARES 和 SPIDER_MIDDLEWARES(包括位置和权重–距离引擎的距离,表示数据是否会越先经过)
-
自定义MiddleWares
1.把多个UA放入settings文件中,赋值给 USER_AGENTS 2.模板中的middlewares文件里面的代码时没有什么用处的,可以全部删除再自定义 3.MiddleWares主要两个用处(自定义: UA池 / 代理IP池) ------>添加自定义的UA,给request的headers赋值即可 class RandomUserAgent (object): def process_request(self, request, spider): useragent = random.choice(USER_AGENTS) request.headers["User-Agent"] = useragent ----->添加代理,需要在request的meta信息中添加proxy字段 class ProxyMiddleware(object): def process_request(self, request, spider): reqeust.meta["proxy"] = "http://124.115.126.76:808" ---->代理的形式为:协议 + ip地址 + 端口 4.Downloader_Middlewares默认方法: process_request(self, request, spider) ->当每个request通过下载中间件时,该方法被调用 process_response(self, request, response, spider) ->当下载器完成http请求时,传递响应给引擎时调用
-
若程序报错must return Response or Request ,got <class ‘NoneType’>则需要加上一下其一:
- return response:返回的响应通过引擎传递给爬虫
- return request:返回的请求通过引擎传递给调度器
-
对于Middlewares,如果还有问题,可以参考官方文档 – Downloader Middleware / 官方文档 – Spider Middleware
-
process_request 不需要return;process_response才需要
requests请求翻页
- 思路
- 找到下一页url地址
- 调用 requests.get(url) —— 传递给调度器
- scrapy 中操作
- 组装称一个requests对象再传给引擎
- scrapy.Request 能构建一个requests,同时指定提取数据的 callback 函数
# 找到下一页的 url 地址 next_url = response.xpath("xpath_expression").extract_first() if next_url != "结束判断条件": next_url = "基础url" + next_url #获取下一页url # 构造一个请求,请求->引擎->调度器->引擎->下载器->响应 下一页爬取 yield scrapy.Request( next_url, # 若获取下一页内容采用同一函数(parse),则继续调用 自身 获取 # 若获取下一页内容采用不同函数(parse1),则直接调用 parse1 获取 callback = self.parse() )
- scrapy.Request() 可选参数
scrapy.Request( url [, callback, method='GET', headers, body, cookies,meta, dont_filter = False] )
- callback : 指定传入的 url 交给哪个解析函数去处理
- meta : 实现再不同的解析函数中传递数据,meta默认会携带部分信息,比如下载延迟,请求深度等
- dont_filter : 让scrapy的去重不会过滤当前url(scrapy 默认会有url去重功能)
Scrapy中的CrawlSpider
- 简介
- 生成 crawl spider 命令
scrapy genspider -t crawl demo baidu.com
- 生成文件代码解析
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class DemoSpider(CrawlSpider): # 继承了CrawlSpider父类
name = 'demo'
allowed_domains = ['baidu.com']
start_urls = ['http://baidu.com/']
# 定义提取url地址规则
rules = (
# rules 可定义多个
# LinkExtractor 连接提取器----提取url地址
# callback 提取出来的url地址的response会交给callback处理
# follow 当前url的响应是否重新经过rules来提取url地址
# callback和follow 可自己选择需不需要写
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
# parse函数有特殊功能,不能定义
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
- 注意点
- 即使在 allow 中 url 不完整,CrawlSpider 也会自动的补充完整再请求
- 不能再有以 parse 为名字的数据提取方法,这个方法已被 CrawlSpider 使用
- 一个 Rule 对象接收很多参数,首先第一个是报假案url规则的 LinkExtractor 对象,常用的还有callback(制定满足规则的 url 的解析函数的字符串)和 follow( response 提取的链接是否需要跟进)
- 不指定 callback 函数的请求下,如果 follow 为 True ,满足该入了的俩还会继续被请求
- 如果多个 Rule 都满足某一个 url,会从 rules 中选择第一个满足的进行操作
- LinkExtractor更多常见参数:
allow: 满足括号中"正则表达式"的URL会被提取,如果为空,则全部匹配
deny: 满足括号中“正则表达式"的URL一定不提取( 优先级高于allow )
allow_domains: 会被提取的链接的 domains.
deny_domains: 一定不会被提取链接的 domains.
restrict_xpaths: 使用 xpath表达式,和 allow 共同作用过滤链接,级 xpath 满足范围内的 url 地址会被提取 - spiders. Rule常见参数:
link_extractor: 是一个 Link Extractor 对象,用于定义需要提取的链接。
callback: 从 link_extractor 中每获取到链接时,参数所指定的值作为回调函数
follow: 是一个布尔( boolean )值,指定了根据该规则从 response 提取的链接是否需要跟进
如果 callback 为 None , follow 默认设置为 True , 否则默认为 False
process_links: 指定该 spider 中哪个的函数将会被调用,从 link_extractor 中获取到链接列表时将会调用该函数,该方法主要用来过滤 url
process_request: 指定该 spider 中哪个的函数将会被调用,该规则提取到每个 request 时都会调用该函致,用来过滤 request
Scrapy模拟登录之携带cookie登录
- 应用场景
- cookie过期时间很长,常见于一些不规范的网站
- 能在cookie过期之前把搜有的数据拿到
- 配合其他程序使用,比如其使用selenium把登陆之后的cookie获取到保存到本地, scrapy发送请求之前先读取本地cookie
- 如何实现
我们定义在spider下的start_urls = [] 都是默认交给 start_requests 处理的,所以如果有必要,可以重写 start_requests 方法
- 代码编写:
- cookie 字符串转化为字典:
cookie = {i.split("=")[0]:i.split("=")[1] for i in cookies.split("; ")}
- 使用
def start_requests(self): cookies = "..." cookies = {i.split("=")[0]:i.split("=")[1] for i in cookies.split("; ")} yield scrapy.Request( self.start_urls[0], callback=self.parse, cookies=cookies )
- 若在 settings 文件中关闭了 COOKIES_ENABLED ,后续的页面请求也会带上传递进去的cookies
- 也可以在 settings 文件中添加 COKIES_DEBUG = True 以查看 cookies 去向
- cookies不能放在 headers 中去请求,否则无效;直接放在 scrapy.Request 中就能获取到cookie
Scrapy模拟登录之发送post请求
- scrapy.Request发送的是get请求;scrapy.FormRequest发送post请求(同时使用formdata来携带post数据)
- 通过发送cookie中的 form_data 数据进行提交(根据网站的Form Data来填写)
def parse(self, response): authenticity_token = response.xpath("xpath_expression").extract_first() utf8 = response.xpath("xpath_expression").extract_first() commit = response.xpath("xpath_expression").extract_first() post_data = dict( login="login_name", password="login_password", authenticity_token=authenticity_token, utf8=utf8, commit=commit ) yield scrapy.FormRequest( "html_url", formdata=post_data, callback=self.after_login ) 类似于: yield scrapy.Request( url, method="POST", body = { } )
- 利用Scrapy中自动找到action中的url和form表单的功能进行账号和密码的填写进行登录操作
TIPS:def parse(self, response): yield scrapy.FormRequest.from_response( response, #自动从response中寻找form表单 formdata={"login":"login_name","password":"your_password"}, callback=self.after_login )
- 如果有多个form表单,可通过加入formname,formid,formnumber,formxpath来选择不同的form表单
scrapy_redis 相关
-
分布式爬虫(多台机器协作)的关键是:共享爬取队列,爬取队列在主机维护,各从机调度器统一从共享队列获取Request,即主机维护爬取队列,从机负责数据抓取、数据处理、数据存储;
-
Redis 是非关系型数据库(Key – Value形式存储),结构灵活;是内存中数据结构存储系统,处理速度快,性能好;提供队列、集合等多重存储结构,方便队列维护
-
scrapy_redis 在 scrapy 基础上实现了更多、更强大的功能,具体体现在:request去重,爬虫持久化,轻松实现分布式
-
如何实现去重:借助Redis集合,在集合中存储每个Request的指纹。在向Request队列中加入Request之前先验证这个Request的指纹是否已经在集合中:
(1)已存在则不添加到队列
(2)不存在则添加入队列并将指纹加入集合 -
如何防止中断(实现持久化):在每台从机Scrapy启动时都会首先判断Redis Request队列是否为空:
(1)不为空则从队列中取得下一个Request执行爬取
(2)为空则重新开始爬取,第一台从机执行爬取向队列中天下Request -
scrapy-redis 下载网址(GitHub)
-
scrapy_redis的爬虫流程:
-
Redis中存在爬过的(用来去重)以及待爬的(可实现断点续爬)指纹集合
动态网站数据抓取
- 一般抓取动态网站,页面展示出来的并不一定是你能够抓取到的,因此,需要分析 Get 请求和返回 Response 对象,仔细找到后台管理返回的数据,再去抓取。
- 一般通过浏览器调试功能中的 Network 抓包去找到数据文件再去分析
列表生成式:
list = [i for I in list if len(i)>0 ] # 对于 list 中的每一项元素 i ,只取长度大于0的元素
list = [re.sub("\xa0|\s","",i) for i in list ] # 对于 list 中的每一项元素i,都把\xa0或\s替换成空字符串(通过re库完成替换操作)
extract() 和 extract_first() 的区别:
如果一个标签下面还有很多个标签
- 用 extract() 方法能获取到所有的元素,并组成一个列表
- 用 extract_first() 方法就多做了一步:将列表中的第一个元素取出,在作用上相当于 extract()[0]
yield 生成器
- 待补充
selector 选择器
- 待补充
实战例子01 ---- 爬取苏宁易购图书
- 步骤
貌似网站已挂,第一个实战项目,以步骤为主
相关代码以截图为主,以供参考
- 爬取信息:书名,大分类,小分类,价格,书的图片封面,作者,出版社,简介
- 分组:大分类 → 小分类 → 列表 → 翻页 → 书本信息 → 价格
- 寻找初始页面:能翻页的大概率为动态页面,打开控制台,找到Network,判断当前页面是否存在需要的数据,比较简单的方法就是直接查找页面中数据
- 创建爬虫项目(start_project);创建爬虫文件(genspider)
- 寻找URL列表:利用 yield 语句传递 url 提取数据
- 进入信息页面:找到图书信息所在标签,利用 xpath 表达式获得数据
- 获取价格数据:当数据不在 html 的标签中时用 正则表达式 提取数据
- 翻页:翻页的数据通过 js 生成(html中没有响应数据),同时 yield 中传出meta与上文 parse_book_list 函数传入的 item 可通过 deepcopy 隔离,即此处的 meta 仅有大分类,小分类和小分类url地址,三项数据;因此上文中的传入item提取则可以通过 deepcopy 隔离提取,让原来的item并没有发生什么变化,这样也能使数据传递更加简单
相当于获得小分类url地址进入列表信息时需要通过翻页操作来获得更多列表项,传至下一页的item只是键值对变得更多了(添加了book_name等),其实影响并不大,因为会被覆盖,但是为了更加的优化,则通过deepcopy隔离开,让函数传入的item并没有增多键值对再传出。
修改后代码为:
- 问题
列表打印后会有元素为空
- 可能是某一些的数据不在定位的标签内
- 因此需要进行判断:若为空则使用另一个表达式
列表中有元素重复
- 因为 scrapy 框架是多线程的,一个爬虫文件内的多个 parse 函数之间可能会同时进行,而 item 在数据还没 保存/打印 之前,若进入了下一个循环,会导致数据被覆盖,就会在最后进行数据 保存/打印 时重复显示,我们看到的数据就会出现重复的数据
- 解决方法:
头导入包:from copy import deepcopy
在 yield 操作时要进行 deepcopy 操作,这样当后一个更改数据时不会影响其他的 item
yield Scrapy.Request(
[“s_href”],
callback=self.parse_book_list,
{“item”: deepcopy(item) }
)
实战例子02 ---- 京东爬虫
- 待更新
实战例子03 ---- 当当爬虫
- 待更新
实战例子04 ---- 亚马逊爬虫
- 待更新