Scrapy 常用方法以及其补充
本文作者:ZiCh
本文链接:https://www.cnblogs.com/zichliang/p/17178868.html
版权声明:未经作者允许严禁转载
1. JsonRequest 使用示例
使用 JsonReuquest发送 JSON POST 请求:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
data = {
'name1': 'value1',
'name2': 'value2',
}
**yield** JsonRequest(url='http://www.xxx.com/xxx/xxx', data=data)
表示HTTP响应的对象,通常下载(由Downloader)并提供给爬行器进行处理。
参数详解
- url (str) -- 此响应的URL
- status (int) -- 响应的HTTP状态。默认为
200
. - headers (dict) -- 此响应的头。dict值可以是字符串(对于单值头)或列表(对于多值头)。
- body (bytes) -- 反应机构。要以字符串形式访问解码文本,请使用
response.text
从编码感知 Response subclass ,如[TextResponse](https://www.osgeo.cn/scrapy/topics/request-response.html?highlight=jsonrequest#scrapy.http.TextResponse)
. - flags (list) -- 是一个列表,其中包含
[Response.flags](https://www.osgeo.cn/scrapy/topics/request-response.html?highlight=jsonrequest#scrapy.http.Response.flags)
属性。如果给定,则将浅复制列表。 - request (scrapy.Request) -- 的初始值
[Response.request](https://www.osgeo.cn/scrapy/topics/request-response.html?highlight=jsonrequest#scrapy.http.Response.request)
属性。这代表[Request](https://www.osgeo.cn/scrapy/topics/request-response.html?highlight=jsonrequest#scrapy.http.Request)
产生了这个响应。 - certificate (twisted.internet.ssl.Certificate) -- 表示服务器的SSL证书的对象。
- ip_address (
[ipaddress.IPv4Address](https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Address)
or[ipaddress.IPv6Address](https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Address)
) -- 从哪个服务器发出响应的IP地址。 - protocol (
[str](https://docs.python.org/3/library/stdtypes.html#str)
) -- 用于下载响应的协议。例如:“HTTP/1.0”、“HTTP/1.1”、“H2”
2. scrapy settings
# -*- coding: utf-8 -*-
# Scrapy settings for companyNews 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
from DBSetting import host_redis,port_redis,db_redis,password_redis
#它是一种可以用于构建用户代理机器人的名称,默认值:'scrapybot'
BOT_NAME = 'companyNews'
# 它是一种含有蜘蛛其中Scrapy将寻找模块列表,默认值: []
SPIDER_MODULES = ['companyNews.spiders']
# 默认: '',使用 genspider 命令创建新spider的模块。
NEWSPIDER_MODULE = 'companyNews.spiders'
#-----------------------日志文件配置-----------------------------------
# 默认: True,是否启用logging。
# LOG_ENABLED=True
# 默认: 'utf-8',logging使用的编码。
# LOG_ENCODING='utf-8'
# 它是利用它的日志信息可以被格式化的字符串。默认值:'%(asctime)s [%(name)s] %(levelname)s: %(message)s'
# LOG_FORMAT='%(asctime)s [%(name)s] %(levelname)s: %(message)s'
# 它是利用它的日期/时间可以格式化字符串。默认值: '%Y-%m-%d %H:%M:%S'
# LOG_DATEFORMAT='%Y-%m-%d %H:%M:%S'
#日志文件名
#LOG_FILE = "dg.log"
#日志文件级别,默认值:“DEBUG”,log的最低级别。可选的级别有: CRITICAL、 ERROR、WARNING、INFO、DEBUG 。
LOG_LEVEL = 'WARNING'
# -----------------------------robots协议---------------------------------------------
# Obey robots.txt rules
# robots.txt 是遵循 Robot协议 的一个文件,它保存在网站的服务器中,它的作用是,告诉搜索引擎爬虫,
# 本网站哪些目录下的网页 不希望 你进行爬取收录。在Scrapy启动后,会在第一时间访问网站的 robots.txt 文件,
# 然后决定该网站的爬取范围。
# ROBOTSTXT_OBEY = True
# 对于失败的HTTP请求(如超时)进行重试会降低爬取效率,当爬取目标基数很大时,舍弃部分数据不影响大局,提高效率
RETRY_ENABLED = False
#请求下载超时时间,默认180秒
DOWNLOAD_TIMEOUT=20
# 这是响应的下载器下载的最大尺寸,默认值:1073741824 (1024MB)
# DOWNLOAD_MAXSIZE=1073741824
# 它定义为响应下载警告的大小,默认值:33554432 (32MB)
# DOWNLOAD_WARNSIZE=33554432
# ------------------------全局并发数的一些配置:-------------------------------
# Configure maximum concurrent requests performed by Scrapy (default: 16)
# 默认 Request 并发数:16
# CONCURRENT_REQUESTS = 32
# 默认 Item 并发数:100
# CONCURRENT_ITEMS = 100
# The download delay setting will honor only one of:
# 默认每个域名的并发数:8
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
# 每个IP的最大并发数:0表示忽略
# CONCURRENT_REQUESTS_PER_IP = 0
# Configure a delay for requests for the same website (default: 0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY 会影响 CONCURRENT_REQUESTS,不能使并发显现出来,设置下载延迟
#DOWNLOAD_DELAY = 3
# Disable cookies (enabled by default)
#禁用cookies,有些站点会从cookies中判断是否为爬虫
# COOKIES_ENABLED = True
# COOKIES_DEBUG = True
# Crawl responsibly by identifying yourself (and your website) on the user-agent
# 它定义了在抓取网站所使用的用户代理,默认值:“Scrapy / VERSION“
#USER_AGENT = ' (+http://www.yourdomain.com)'
# 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',
}
# Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES = {
'companyNews.middlewares.UserAgentmiddleware': 401,
'companyNews.middlewares.ProxyMiddleware':426,
}
# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
'companyNews.middlewares.UserAgentmiddleware': 400,
'companyNews.middlewares.ProxyMiddleware':425,
# 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware':423,
# 'companyNews.middlewares.CookieMiddleware': 700,
}
MYEXT_ENABLED=True # 开启扩展
IDLE_NUMBER=12 # 配置空闲持续时间单位为 360个 ,一个时间单位为5s
# Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
# 在 EXTENSIONS 配置,激活扩展
EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
'companyNews.extensions.RedisSpiderSmartIdleClosedExensions': 500,
}
# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
# 注意:自定义pipeline的优先级需高于Redispipeline,因为RedisPipeline不会返回item,
# 所以如果RedisPipeline优先级高于自定义pipeline,那么自定义pipeline无法获取到item
ITEM_PIPELINES = {
#将清除的项目在redis进行处理,# 将RedisPipeline注册到pipeline组件中(这样才能将数据存入Redis)
# 'scrapy_redis.pipelines.RedisPipeline': 400,
'companyNews.pipelines.companyNewsPipeline': 300,# 自定义pipeline视情况选择性注册(可选)
}
# Enable and configure HTTP caching (disabled by default)
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
# ----------------scrapy默认已经自带了缓存,配置如下-----------------
# 打开缓存
#HTTPCACHE_ENABLED = True
# 设置缓存过期时间(单位:秒)
#HTTPCACHE_EXPIRATION_SECS = 0
# 缓存路径(默认为:.scrapy/httpcache)
#HTTPCACHE_DIR = 'httpcache'
# 忽略的状态码
#HTTPCACHE_IGNORE_HTTP_CODES = []
# HTTPERROR_ALLOWED_CODES = [302, 301]
# 缓存模式(文件缓存)
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
#-----------------Scrapy-Redis分布式爬虫相关设置如下--------------------------
# Enables scheduling storing requests queue in redis.
#启用Redis调度存储请求队列,使用Scrapy-Redis的调度器,不再使用scrapy的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# Ensure all spiders share same duplicates filter through redis.
#确保所有的爬虫通过Redis去重,使用Scrapy-Redis的去重组件,不再使用scrapy的去重组件
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 默认请求序列化使用的是pickle 但是我们可以更改为其他类似的。PS:这玩意儿2.X的可以用。3.X的不能用
# SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"
# 使用优先级调度请求队列 (默认使用),
# 使用Scrapy-Redis的从请求集合中取出请求的方式,三种方式择其一即可:
# 分别按(1)请求的优先级/(2)队列FIFO/(先进先出)(3)栈FILO 取出请求(先进后出)
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
# 可选用的其它队列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue'
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue'
# Don't cleanup redis queues, allows to pause/resume crawls.
#不清除Redis队列、这样可以暂停/恢复 爬取,
# 允许暂停,redis请求记录不会丢失(重启爬虫不会重头爬取已爬过的页面)
#SCHEDULER_PERSIST = True
#----------------------redis的地址配置-------------------------------------
# Specify the full Redis URL for connecting (optional).
# If set, this takes precedence over the REDIS_HOST and REDIS_PORT settings.
# 指定用于连接redis的URL(可选)
# 如果设置此项,则此项优先级高于设置的REDIS_HOST 和 REDIS_PORT
# REDIS_URL = 'redis://root:密码@主机IP:端口'
# REDIS_URL = 'redis://root:123456@192.168.8.30:6379'
REDIS_URL = 'redis://root:%s@%s:%s'%(password_redis,host_redis,port_redis)
# 自定义的redis参数(连接超时之类的)
REDIS_PARAMS={'db': db_redis}
# Specify the host and port to use when connecting to Redis (optional).
# 指定连接到redis时使用的端口和地址(可选)
#REDIS_HOST = '127.0.0.1'
#REDIS_PORT = 6379
#REDIS_PASS = '19940225'
#-----------------------------------------暂时用不到-------------------------------------------------------
# 它定义了将被允许抓取的网址的长度为URL的最大极限,默认值:2083
# URLLENGTH_LIMIT=2083
# 爬取网站最大允许的深度(depth)值,默认值0。如果为0,则没有限制
# DEPTH_LIMIT = 3
# 整数值。用于根据深度调整request优先级。如果为0,则不根据深度进行优先级调整。
# DEPTH_PRIORITY=3
# 最大空闲时间防止分布式爬虫因为等待而关闭
# 这只有当上面设置的队列类是SpiderQueue或SpiderStack时才有效
# 并且当您的蜘蛛首次启动时,也可能会阻止同一时间启动(由于队列为空)
# SCHEDULER_IDLE_BEFORE_CLOSE = 10
# 序列化项目管道作为redis Key存储
# REDIS_ITEMS_KEY = '%(spider)s:items'
# 默认使用ScrapyJSONEncoder进行项目序列化
# You can use any importable path to a callable object.
# REDIS_ITEMS_SERIALIZER = 'json.dumps'
# 自定义redis客户端类
# REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient'
# 如果为True,则使用redis的'spop'进行操作。
# 如果需要避免起始网址列表出现重复,这个选项非常有用。开启此选项urls必须通过sadd添加,否则会出现类型错误。
# REDIS_START_URLS_AS_SET = False
# RedisSpider和RedisCrawlSpider默认 start_usls 键
# REDIS_START_URLS_KEY = '%(name)s:start_urls'
# 设置redis使用utf-8之外的编码
# REDIS_ENCODING = 'latin1'
# Disable Telnet Console (enabled by default)
# 它定义是否启用telnetconsole,默认值:True
#TELNETCONSOLE_ENABLED = False
# Enable and configure the AutoThrottle extension (disabled by default)
# See http://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
#禁止重定向
#除非您对跟进重定向感兴趣,否则请考虑关闭重定向。 当进行通用爬取时,一般的做法是保存重定向的地址,并在之后的爬取进行解析。
# 这保证了每批爬取的request数目在一定的数量, 否则重定向循环可能会导致爬虫在某个站点耗费过多资源。
# REDIRECT_ENABLED = False
3. Scrapy——LinkExtractor
- 提取链接的方法
在爬取一个网站时,想要爬取的数据通常分布在多个页面中,每个页面包含一部分数据以及到其他页面的链接,提取页面中数据的方法大家已经掌握,提取链接有使用 Selector 和使用 LinkExtractor 两种方法。
(1)使用Selector
因为链接也是页面中的数据,所以可以使用与提取数据相同的方法进行提取,在提取少量链接或提取规则比较简单时,使用 Selector 就足够了。例如我们在项目 toscrapy 中编写爬虫 books.py 的数据解析函数 parse() 时,就是用了 Selector 提取了下一个页面的链接,代码如下:
class BooksSpider(scrapy.Spider):
name = 'books'
allowed_domains = ['books.toscrape.com']
start_urls = [' http://books.toscrape.com/']
def parse(self, response):
......
# 提取链接
next_url = response.css('ul.pager li.next a::attr(href)').extract_first()
if next_url:
next_url = response.urljoin(next_url)
yield scrapy.Request(next_url, callback=self.parse)
第一种方法我们早已掌握,下面学习如何使用 LinkExtractor 提取链接。
(2)使用LinkExtractor
Scrapy 提供了一个专门用于提取链接的类 LinkExtractor,在提取大量链接或提取规则比较复杂时,使用 LinkExtractor 更加方便。
LinkExtractor 的使用非常简单,我们通过将上述代码中的 Selector 替换成 LinkExtractor 进行讲解,代码如下:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.linkextractors import LinkExtractor
class BooksSpider(scrapy.Spider):
name = 'books'
allowed_domains = ['books.toscrape.com']
start_urls = [' http://books.toscrape.com/']
def parse(self, response):
...
# 提取链接
le = LinkExtractor(restrict_css='ul.pager li.next')
links = le.extract_links(response)
if links:
next_url = links[0].url
yield scrapy.Request(next_url, callback=self.parse)
代码解析如下:
Step 1:创建一个 LinkExtractor 对象,使用一个或多个构造器参数描述提取规则。这里传递给 restrict_css 参数一个 CSS 选择器表达式。它描述出下一页链接所在的区域(在 li.next 下)。
Step 2:调用 LinkExtractor 对象的 extract_links 方法传入一个 Response 对象,该方法依据创建对象时所描述的提取规则, 在 Response 对象所包含的页面中提取链接,最终返回一个列表,其中的每一个元素都是一个 Link 对象,即提取到的一个链接。
Step 3:由于页面中的下一页链接只有一个,因此用 links[0] 获取 LinkExtractor 对象,LinkExtractor 对象的 url 属性便是链接页面的绝对 url 地址(无须再调用 response.urljoin 方法),用其构造 Request 对象并提交。
- LinkExtractor 提取链接的规则
接下来,我们来学习使用 LinkExtractor 的构造器参数描述提取链接的规则。
首先我们创建两个包含多个链接的HTML页面,作为 LinkExtractor 提取链接的示例网页:
<!--
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
-->
<html>
<body>
<div id="top">
<p>下面是一些站内链接</p>
<a class="internal" href="/intro/install.html">Installation guide</a>
<a class="internal" href="/intro/tutorial.html">Tutorial</a>
<a class="internal" href="../examples.html">Examples</a>
</div>
<div id="bottom">
<p>下面是一些站外链接</p>
<a href="http://stackoverflow.com/tags/scrapy/info">StackOverflow</a>
<a href="https://github.com/scrapy/scrapy">Fork on Github</a>
</div>
</body>
</html>
<!-- example2.html -->
<html>
<head>
<script type='text/javascript' src='/js/app1.js'/>
<script type='text/javascript' src='/js/app2.js'/>
</head>
<body>
<a href="/home.html">主页</a>
<a href="javascript:goToPage('/doc.html'); return false">文档</a>
<a href="javascript:goToPage('/example.html'); return false">案例</a>
</body>
</html>
LinkExtractor 构造器的所有参数都有默认值,如果构造对象时不传递任何参数(使用默认值),就提取页面中所有链接。例如以下代码将提取页面 example1.html 中的所有链接:
下面依次介绍 LinkExtractor 构造器的各个参数:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
html1 = open('LE_Example1.html').read()
response1 = HtmlResponse(url='http://example1.com', body=html1, encoding='utf8')
pattern = '/intro/.+\.html$'
le = LinkExtractor(allow=pattern)
links = le.extract_links(response1)
print([link.url for link in links])
# 运行结果:
[' http://example1.com/intro/install.html', ' http://example1.com/intro/tutorial.html']
(1)allow
allow 接收一个正则表达式或一个正则表达式列表,提取绝对 url 与正则表达式匹配的链接,如果该参数为空(默认),就提取全部链接。
例1:提取页面 example1.html 中路径以 /intro 开始的链接:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
html1 = open('LE_Example1.html').read()
response1 = HtmlResponse(url='http://example1.com', body=html1, encoding='utf8')
pattern = '/intro/.+\.html$'
le = LinkExtractor(allow=pattern)
links = le.extract_links(response1)
print([link.url for link in links])
# 运行结果:
[' http://example1.com/intro/install.html', ' http://example1.com/intro/tutorial.html']
(2)deny
接收一个正则表达式或一个正则表达式列表,与 allow 相反,排除绝对 url 与正则表达式匹配的链接。
例2:提取页面 example1.html 中所有站外链接(即排除站内链接):
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
from urllib.parse import urlparse
html1 = open('LE_Example1.html').read()
response1 = HtmlResponse(url='http://example1.com', body=html1, encoding='utf8')
pattern = '^' + urlparse(response1.url).geturl()
le = LinkExtractor(deny=pattern)
links = le.extract_links(response1)
print([link.url for link in links])
# 运行结果:
[' http://stackoverflow.com/tags/scrapy/info', ' https://github.com/scrapy/scrapy']
(3)allow_domains
接收一个域名或一个域名列表,提取到指定域的链接。
例3::提取页面 example1.html 中所有到 github.com 和 stackoverflow.com 这两个域的链接:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
html1 = open('LE_Example1.html').read()
response1 = HtmlResponse(url='http://example1.com', body=html1, encoding='utf8')
domains = ['github.com', 'stackoverflow.com']
le = LinkExtractor(allow_domains=domains)
links = le.extract_links(response1)
print([link.url for link in links])
# 运行结果:
[' http://stackoverflow.com/tags/scrapy/info', ' https://github.com/scrapy/scrapy']
(4)deny_domains
接收一个域名或一个域名列表,与 allow_domains 相反,排除到指定域的链接。
例4:提取页面 example1.html 中除了到 github.com 域以外的链接:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
html1 = open('LE_Example1.html').read()
response1 = HtmlResponse(url='http://example1.com', body=html1, encoding='utf8')
le = LinkExtractor(deny_domains='github.com')
links = le.extract_links(response1)
print([link.url for link in links])
# 运行结果:
[' http://example1.com/intro/install.html', ' http://example1.com/intro/tutorial.html', ' http://example1.com/examples.html', ' http://stackoverflow.com/tags/scrapy/info']
(5)restrict_xpaths
接收一个 XPath 表达式或一个 XPath 表达式列表,提取 XPath 表达式选中区域下的链接。
例5:提取页面 example1.html 中
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
html1 = open('LE_Example1.html').read()
response1 = HtmlResponse(url='http://example1.com', body=html1, encoding='utf8')
le = LinkExtractor(restrict_xpaths='//div[@id="top"]')
links = le.extract_links(response1)
print([link.url for link in links])
# 运行结果:
[' http://example1.com/intro/install.html', ' http://example1.com/intro/tutorial.html', ' http://example1.com/examples.html']
(6)restrict_css
接收一个 CSS 选择器或一个 CSS 选择器列表,提取 CSS 选择器选中区域下的链接。
例6:提取页面 example1.html 中
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
html1 = open('LE_Example1.html').read()
response1 = HtmlResponse(url='http://example1.com', body=html1, encoding='utf8')
le = LinkExtractor(restrict_css='div#bottom')
links = le.extract_links(response1)
print([link.url for link in links])
# 运行结果:
[' http://stackoverflow.com/tags/scrapy/info', ' https://github.com/scrapy/scrapy']
(7)tags
接收一个标签(字符串)或一个标签列表,提取指定标签内的链接,默认为 ['a', 'area'] 。
(8)attrs
接收一个属性(字符串)或一个属性列表,提取指定属性内的链接,默认为[‘href’]。
例8:提取页面 example2.html 中引用 JavaScript 文件的链接:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
html2 = open('LE_Example2.html').read()
response2 = HtmlResponse(url='http://example2.com', body=html2, encoding='utf8')
le = LinkExtractor(tags='script', attrs='src')
links = le.extract_links(response2)
print([link.url for link in links])
# 运行结果:
[' http://example2.com/js/app1.js', ' http://example2.com/js/app2.js']
(9)process_value
接收一个形如 func(value) 的回调函数。如果传递了该参数,LinkExtractor 将调用该回调函数对提取的每一个链接(如 a 的 href )进行处理,回调函数正常情况下应返回一个字符串(处理结果),想要抛弃所处理的链接时,返回 None。
例9:在页面 example2.html 中,某些 a 的 href 属性是一段 JavaScript 代码,代码中包含了链接页面的实际 url 地址,此时应对链接进行处理,提取页面 example2.html 中所有实际链接:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http import HtmlResponse
from scrapy.linkextractors import LinkExtractor
import re
html2 = open('LE_Example2.html').read()
response2 = HtmlResponse(url='http://example2.com', body=html2, encoding='utf8')
def process(value):
m = re.search("javascript:goToPage\('(.*?)'", value)
# 如果匹配,就提取其中 url 并返回,如果不匹配则返回原值
if m:
value = m.group(1)
return value
le = LinkExtractor(process_value=process)
links = le.extract_links(response2)
print([link.url for link in links])
# 运行结果:
[' http://example2.com/doc.html', ' http://example2.com/example.html']j
4. Scrapy 选择器
一、简介
前面介绍了scrapy命令和Scrapy处理流程与重要组件
这里介绍一下Scrapy的Selector,Scrapy的Selector和Beautifulsoup非常像,关于Beautifulsoup可以参考BeautifuSoup实用方法属性总结 和BeautifulSoup详解
先来看一下Selector的知识点:
二、xpath
我们先介绍一下xpath,因为xpath语法比较简洁,并且如果能够灵活应用的话,可以简化我们提取HTML内容的复杂度。
符号 | 说明 |
---|---|
/ | 从根节点选取,使用绝对路径,路径必须完全匹配 |
// | 从整个文档中选取,使用相对路径 |
. | 从当前节点开始选取 |
… | 从当前节点父节点开始选取 |
@ | 选取属性 |
光看说明有些抽象,我们通过一个例子来简单说明一下:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy import Selector
content = '''
<div>
<p>out inner div p</p>
<div id="inner"><p>in inner div p</p></div>
</div>
<p>out div p</p>
'''
selector = Selector(text=content)
# 在整个文档中选取id为inner的div节点
inner_div_sel = selector.xpath("//div[@id='inner']")
# 获取整个文档中的p节点的文本
print(inner_div_sel.xpath('//p/text()').getall())
# 从inner div节点的父节点开始获取所有p节点的文本
print(inner_div_sel.xpath('..//p/text()').getall())
# 从inner div节点开始获取所有p节点的文本
print(inner_div_sel.xpath('.//p/text()').getall())
5. Scrapy——os批量更新
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
import os
from scrapy import spiderloader
from scrapy.utils.project import get_project_settings
from scrapy.utils.log import configure_logging
settings = get_project_settings()
spider_loader = spiderloader.SpiderLoader.from_settings(settings)
spiders = spider_loader.list()
print(spiders)
# if __name__ == '__main__':
# settings = get_project_settings()
# print(settings)
# print(settings.get('BOT_NAME'))
for spider in spiders:
os.system("scrapy crawl {}".format(spider))
6. scrapy 解析xml格式的数据
XMLFeedSpider 主要用于 解析 xml格式的数据
创建一个scrapy 框架
scrapy startproject xxx
创建一个spider
scrapy genspider -t xmlfeed ZhaoYuanCity_2_GovPro(名字) xxx.com(网站名)
解析的例子为招远市人民政府的数据
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
import re
import scrapy
from scrapy.spiders import XMLFeedSpider
from curreny.items import CurrenyItem
class Zhaoyuancity2GovproSpider(XMLFeedSpider):
name = 'ZhaoYuanCity_2_GovPro'
# allowed_domains = ['xxx.com']
start_urls = ['http://www.zhaoyuan.gov.cn/module/web/jpage/dataproxy.jsp?page=1&webid=155&path=http://www.zhaoyuan.gov.cn/&columnid=48655&unitid=180549&webname=%25E6%258B%259B%25E8%25BF%259C%25E5%25B8%2582%25E6%2594%25BF%25E5%25BA%259C&permiss']
iterator = 'iternodes' # you can change this; see the docs
itertag = 'datastore' # change it accordingly
def parse_node(self, response, selector):
# 用css 获取 一个列表
source_list = selector.css('recordset record::text').extract()
for li in source_list:
# 用正则解析url 我们去里面获取时间标题和内容
url= re.search(r'href=\"(.*\.html)\"',li).group(1)
yield scrapy.Request(
url=url,
callback=self.parse
)
def parse(self,response):
# 调用item
item = {}
# 写入链接提取器中获取到的url
item['title_url'] = response.url
# 标题名
item['title_name'] = response.css('meta[name="ArticleTitle"]::attr(content)').get()
# 标题时间
item['title_date'] = response.css('meta[name="pubdate"]::attr(content)').get()
# 内容提取 含源码
item['content_html'] = response.css('.main').get()
# 交给item处理
yield item
最后运行项目
scrapy crawl ZhaoYuanCity_2_GovPro --nolog
注释:
- iterator属性:设置使用的迭代器,默认为“iternodes”(一个基于正则表达式的高性能迭代器),除此之外还有“html”和“xml”迭代器;
- itertag:设置开始迭代的节点;
- parse_node方法:在节点与所提供的标签名相符合时被调用,在其中定义信息提取和处理的操作;
- namespaces属性:以列表形式存在,主要定义在文档中会被蜘蛛处理的可用命令空间;
- parse方法: 解析数据 发起正常请求
- **adapt_response(response)方法:在spider分析响应前被调用;
- **process_results(response, results)方法:在spider返回结果时被调用,主要对结果在返回前进行最后的处理。
7. scrapy 运行 其中的spider文件
from scrapy import cmdline
cmdline.execute(['scrapy','crawl','NeiMengGuInvestPro'])
8. scrapy 批量执行 使用scrapy command
在settings中 写入
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.commands import ScrapyCommand
from scrapy.utils.project import get_project_settings
class Command(ScrapyCommand):
requires_project = True
def syntax(self):
return '[options]'
def short_desc(self):
return 'Runs all of the spiders'
def run(self, args, opts):
spider_list = self.crawler_process.spiders.list()
print('爬取开始')
for name in spider_list:
self.crawler_process.crawl(name, **opts.__dict__)
self.crawler_process.start()
9. 获取文件名 并且运行
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
...
if __name__ == '__main__':
import sys
import os
from scrapy import cmdline
file_name = os.path.basename(sys.argv[0])
file_name=file_name.split(".")[0]
cmdline.execute(['scrapy', 'crawl', file_name])
10. scrapy urljoin
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
next_page_url = response.xpath('...').extract() #搞到拼接的变动的参数内容
if next_page_url is not None:
yield scrapy.Request(response.urljoin(next_page_url))
post_urls=response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()
for post_url in post_urls:
yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)
11. scrapy 的 re_frist 方法
re_first()用来返回第一个匹配的字符串,就在re的基础上提取一个数据而已,而re可以提取多条数据。
re_first 与 extract_first('')都是获取列表的第一项, 而re_first('(\d+)') 是利用正则获取列表第一项的数字
例如:response.xpath('//a[contains(@href, "image")]/text()').re(r'Name:\s(.)')
12. scrapy 获取实时cookie
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.http.cookies import CookieJar
cookie_jar = CookieJar()
cookie_jar.extract_cookies(response, response.request)
print(cookie_jar)
cookie_dict = dict()
cookie_list =''
for k, v in cookie_jar._cookies.items():
for i, j in v.items():
for m, n in j.items():
cookie_dict[m] = n.value
for i,j in cookie_dict.items():
print(i,'----------------',j)
Cookie1 = response.request.headers.getlist('Cookie')
13. 这里有两个css选择器的扩展语法
就是 ::text 和 ::attr 分别获取标签文本和属性。属性还可以用这种方式获得:
response.css('li.next a').attrib['href']
14. Scrapy设置下载延时和自动限速
Scrapy设置下载延时和自动限速
DOWNLOAD_DELAY 在settings.py文件中设置
#延时2秒,不能动态改变,时间间隔固定,容易被发现,导致ip被封
DOWNLOAD_DELAY=2
# RANDOMIZE_DOWNLOAD_DELAY 在settings.py文件中设置
# 启用后,当从相同的网站获取数据时,Scrapy将会等待一个随机的值,延迟时间为0.5到1.5之间的一个随机值乘以DOWNLOAD_DELAY
RANDOMIZE_DOWNLOAD_DELAY=True
# 自动限速扩展 在settings.py中配置
AUTOTHROTTLE_ENABLED #默认为False,设置为True可以启用该扩展
AUTOTHROTTLE_START_DELAY #初始下载延迟,单位为秒,默认为5.0
AUTOTHROTTLE_MAX_DELAY #设置在高延迟情况下的下载延迟,单位为秒,默认为60
AUTOTHROTTLE_DEBUG #用于启动Debug模式,默认为False
CONCURRENT_REQUESTS_PER_DOMAIN #对单个网站进行并发请求的最大值,默认为8
CONCURENT_REQUESTS_PER_IP #对单个IP进行并发请求的最大值,如果非0,则忽略CONCURRENT_REQUESTS_PER_DOMAIN设定,使用该IP限制
15. 在spider中修改settings中的配置
custom_settings = {
'HTTPERROR_ALLOWED_CODES': [404,302, 301],
}
16. Scrapy命令
commands 作用 命令作用域
crawl 使用一个spider开始爬取任务 项目内
check 代码语法检查 项目内
list 列出当前项目中所有可用的spiders ,每一行显示一个spider 项目内
edit 在命令窗口下编辑一个爬虫 项目内
parse 用指定spider方法来访问URL 项目内
bench 测试当前爬行速度 全局
fetch 使用Scrapy downloader获取URL 全局
genspider 使用预定义模板生成一个新的spider 全局
runspider Run a self-contained spider (without creating a project) 全局
settings 获取Scrapy配置信息 全局
shell 命令行交互窗口下访问URL 全局
startproject 创建一个新项目 全局
version 打印Scrapy版本 全局
view 通过浏览器打开URL,显示内容为Scrapy实际所见 全局
scrapy genspider -h
Usage
=====
scrapy genspider [options] <name> <domain>
Generate new spider using pre-defined templates
Options
=======
--help, -h show this help message and exit
--list, -l List available templates
--edit, -e Edit spider after creating it
--dump=TEMPLATE, -d TEMPLATE
Dump template to standard output
--template=TEMPLATE, -t TEMPLATE
Uses a custom template.
--force If the spider already exists, overwrite it with the
template
Global Options
--------------
--logfile=FILE log file. if omitted stderr will be used
--loglevel=LEVEL, -L LEVEL
log level (default: DEBUG)
--nolog disable logging completely
--profile=FILE write python cProfile stats to FILE
--pidfile=FILE write process ID to FILE
--set=NAME=VALUE, -s NAME=VALUE
set/override setting (may be repeated)
--pdb enable pdb on failure
17. scrapy 传递 params
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from urllib.parse import urlencode
params = {
'wbtreeid': '5571',
'searchtext': '',
'wsbslistCURURI': 'ED1C87781DBB6EE748D288AAF4957433',
'wsbslistKEYTYPES': '4,4,4,12,12,93',
'actiontype': '',
'wsbslistORDER': 'desc',
'wsbslistORDERKEY': 'wbdate',
'wsbslistCountNo': '20',
'wsbslistNOWPAGE': str(num),
'wsbslistPAGE': '0',
'wsbslistrowCount': '3545'
}
url = "https://zwfw.nx.gov.cn/jfpt/newslist_ycs.jsp?" + urlencode(params)
18. scrapy 给单个请求设置超时时间
在请求中
meta = {'download_timeout':30}
19.Scrapy框架的代理使用
中间件添加代理
首先在中间件middlewares.py中,在最后加入如下代码:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
class ProxyMiddleware(object):
def process_request(self,request,spider):
entry = 'http://{}:{}@{}:{}'.format("账户", "密码","host","port")
request.meta["proxy"] = entry
然后在setting.py中设置优先级:
DOWNLOADER_MIDDLEWARES = {
'你的项目名.middlewares.ProxyMiddleware': 100,
}
spider中设置代理
entry = 'http://{}-zone-custom:{}@proxy.ipidea.io:2334'.format("帐号", "密码")
# api
# entry = 'http://{}'.format("api获取的ip代理")
# 传参meta迭代下一个方法
for url in self.starturl:
yield scrapy.Request(url,meta={"proxy":entry})
20. scrapy retry 错误重试设置
有时候用scrapy爬虫的时候会遇到请求某些url的时候发生异常的情况(多半是因为代理ip抽风了),这时候就可以设置一下retry让它自动重试,很简单。
settings中间件设置
看了下官网
那么很方便的,我们也可以在settings里这么写
DOWNLOADER_MIDDLEWARES = {
'rent.middlewares.ProxyMiddleWare': 700,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
}
其中第一个是我自己定义的代理ip的中间件,像官网一样proxy中间件的优先级数字应该比retry更大。(数字越大越先执行,想想如果你遇到一个抽风的代理ip,然后你开始retry,如果retry比proxy先执行,那么你就会不停用这个抽风的ip去重试,毫无意义;反之,因为我在proxy中间件有些如果response不是200就换个ip,这样的话就还算合理)
settings参数设置
最后需要在settings里再加几行
# Retry settings
RETRY_ENABLED = True
RETRY_TIMES = 5 # 想重试几次就写几
# 下面这行可要可不要
RETRY_HTTP_CODES = [500, 502, 503, 504, 408]
最下面那行如果需要指定一些code来retry的话,可以写上,否则不用写,我写的这几个就是不写的时候默认的。
21. Scrapy结合Redis实现增量爬取
Scrapy适合做全量爬取,但是,我们不是一次抓取完就完事了。很多情况,我们需要持续的跟进抓取的站点,增量抓取是最需要的。
Scrapy与Redis配合,在写入数据库之前,做唯一性过滤,实现增量爬取。
一、官方的去重Pipeline
官方文档中有一个去重的过滤器:
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
from scrapy.exceptions import DropItem
class DuplicatesPipeline(object):
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
if item['id'] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item['id'])
return item
官方的这个过滤器的缺陷是只能确保单次抓取不间断的情况下去重,因为其数据是保存在内存中的,当一个爬虫任务跑完后程序结束,内存就清理掉了。再次运行时就失效了。
二、基于Redis的去重Pipeline
为了能够多次爬取时去重,我们考虑用Redis,其快速的键值存取,对管道处理数据不会产生多少延时。
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
import pandas as pd
import redis
redis_db = redis.Redis(host=settings.REDIS_HOST, port=6379, db=4, password=settings.REDIS_PWD)
redis_data_dict = "f_uuids"
class DuplicatePipeline(object):
"""
去重(redis)
"""
def __init__(self):
if redis_db.hlen(redis_data_dict) == 0:
sql = "SELECT uuid FROM f_data"
df = pd.read_sql(sql, engine)
for uuid in df['uuid'].get_values():
redis_db.hset(redis_data_dict, uuid, 0)
def process_item(self, item, spider):
if redis_db.hexists(redis_data_dict, item['uuid']):
raise DropItem("Duplicate item found:%s" % item)
return item
- 首先,我们定义一个redis实例: redis_db和redis key:redis_data_dict。
- 在DuplicatePipeline的初始化函数init()中,对redis的key值做了初始化。当然,这步不是必须的,你可以不用实现。
- 在process_item函数中,判断redis的hash表中存在该值uuid,则为重复item。
至于redis中为什么没有用list而用hash? 主要是因为速度,hash判断uuid是否存在比list快好几个数据级。
特别是uuid的数据达到100w+时,hash的hexists函数速度优势更明显。
最后别忘了在settings.py中加上:
三、总结
本文不是真正意义上的增量爬取,而只是在数据存储环节,对数据唯一性作了处理,当然,这样已经满足了大部分的需求。
后续我会实现不需要遍历所有的网页,判断抓取到所有最新的item,就停止抓取。敬请关注!
22. scrapy如何使用同一个session来访问几个url
需要提交的表单里面有个验证码,我需要先把验证码下下来再转换成文本放上去,然后 post 到一个 URL 里。这里面应该要用同一个 session 才可以完成。 requests 里面用 session 就很方便,想请问下在 scrapy 里怎么使用同一个 session?
这样用meta不行。
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
def start_requests(self):
cookie_jar = CookieJar()
yield scrapy.Request(
self.getUrl,
meta={'cookiejar': cookie_jar},
callback=self.downloadPic)
def downloadPic(self, response):
yield scrapy.Request(self.vcodeUrl, meta={'cookiejar': response.meta['cookiejar']}, callback=self.getAndHandlePic)
def getAndHandlePic(self, response):
# handle picture, not the point here
pic = self.handlePic(response.body)
yield FormRequest(self.postUrl, formdata={'a':a, 'pic':pic}, meta={'cookiejar': response.meta['cookiejar']}, callback=self.parse)
def parse(self, response):
# do process source code
23. scrapy 拿到2进制数据
直接用 response.body
24. 快速请求链接 并保存图片
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 13:12
# @Author : lzc
# @Email : hybpjx@163.com
# @blogs : https://www.cnblogs.com/zichliang
# @Software: PyCharm
import urllib.request
url = "https://www.lnzwfw.gov.cn/hz_tzxm_root/userCenter/rand.html"
urllib.request.urlretrieve(url=url, filename="captcha.png")