Scrapy
简介
Scrapy是一个用Python编写的高效且结构化的网页抓取框架,广泛应用于数据挖掘、网站监测和自动化测试。它的设计初衷是为了方便用户爬取网站数据和提取结构化数据,但它的用途不仅限于网页抓取,还包括处理API返回的数据(如Amazon Associates Web Services)或进行更广泛的网络爬取任务。
Scrapy的一个关键特性是它使用了Twisted异步网络库来处理网络通信。
信息
- 异步编程:
- 在传统的同步编程模型中,任务按顺序一个接一个地执行。如果一个任务需要等待(例如,等待网络响应),程序将在此期间停止执行后续任务。
- 异步编程允许程序在等待一个任务完成的同时继续执行其他任务。这种方式非常适合处理I/O密集型任务,比如网络请求,因为程序不需要在每个请求完成时都暂停执行。
- Twisted异步网络库:
- Twisted是一个事件驱动的网络编程框架,专为Python设计。它支持多种协议,可以用于创建高性能的网络服务器和客户端。
- 在Scrapy中,Twisted用于处理网络请求。当Scrapy发出一个请求并等待服务器响应时,它不会阻塞整个爬虫的进程。相反,Scrapy可以利用这段时间执行其他任务,如处理已经收到的数据或发出更多的网络请求。
Scrapy中的应用:
- Scrapy利用Twisted的异步特性来提高爬虫的效率。它能够同时处理多个网络请求,而不会因为单个请求的延迟而阻塞整个爬虫。
- 这使得Scrapy非常适合执行大规模的网络抓取任务,因为它可以有效地利用网络资源和处理能力,同时维持高效率的数据收集。
使用原因:
1.为了更利于我们将精力集中在请求与解析上
2.企业级的要求
1 | flowchart TD |
运行流程
Scrapy框架的体系结构
Scrapy框架的体系结构由几个重要组件组成,这些组件协同工作,实现了从发送请求到提取数据再到存储数据的整个流程:
- 引擎(Engine): Scrapy引擎是框架的核心,负责控制数据流在各组件之间的流动,触发事件。
- 调度器(Scheduler): 负责接收引擎发送的请求,并将它们加入队列中。在引擎请求新请求时,调度器发送下一个要抓取的请求给引擎。
- 下载器(Downloader): 负责从Internet上下载网页内容,并将其封装为响应对象,然后返回给引擎。
- 蜘蛛(Spiders): 是用户编写用来解析响应并从中提取数据(提取的项目)或额外的跟进URL的类。
- 项目管道(Item Pipeline): 负责处理由蜘蛛提取出来的项目,通常包括清洗、验证和存储等。
- 下载器中间件(Downloader Middlewares): 位于引擎和下载器之间,处理引擎和下载器之间的请求和响应。
- 蜘蛛中间件(Spider Middlewares): 位于引擎和蜘蛛之间,处理蜘蛛的输入(响应)和输出(项目和新的请求)。
信息
Downloader Middleware(下载中间件)
这些钩子允许处理、修改和自定义发往下载器的请求以及从下载器返回的响应。下载中间件在Scrapy的请求/响应处理流程中提供了几个关键的扩展点。例如,可以使用它来添加HTTP头部到请求中,或者处理从网站返回的HTTP响应。这些中间件的用途包括:
- 在请求发送到下载器之前修改或过滤请求。
- 在响应发送到爬虫之前处理或修改响应。
- 直接生成新的请求,而不是处理当前的响应。
- 选择性地放弃某些请求
Spider Middleware(爬虫中间件)
爬虫中间件则是处理传入响应、传出项目(item)和请求的钩子。这些中间件在Scrapy的数据处理流程中处于核心位置,允许用户对爬虫的输入和输出进行细致的控制。使用爬虫中间件,可以:
- 在爬虫处理回调之后处理请求或项目。
- 修改或过滤开始请求(由
start_requests
方法生成的请求)。 - 处理由于响应内容引发的异常,根据响应内容调用错误回调(errback)。
数据流过程
- 启动过程:
- Scrapy开始运行,初始化了调度器、下载器以及蜘蛛。
- 爬取过程:
- 引擎向蜘蛛请求第一个要爬取的URL。
- 蜘蛛返回第一个要爬取的请求给引擎。
- 引擎将请求发送到调度器,并请求下一个URL。
- 调度器返回下一个要爬取的请求给引擎。
- 下载过程:
- 引擎从调度器中取出请求,通过下载器中间件发送到下载器。
- 下载器处理请求,返回响应给引擎。
- 引擎收到响应后,通过蜘蛛中间件发送给相应的蜘蛛处理。
- 解析和提取数据:
- 蜘蛛处理响应,提取数据并生成新的请求,返回给引擎。
- 引擎将提取的数据发送到项目管道,并将新的请求发送到调度器。
- 项目管道处理提取出的数据。
- 循环处理:
- 以上过程在整个爬取过程中不断重复,直到调度器中没有更多的请求。
- 关闭过程:
- 当所有预定的URL都被爬取,且调度器中没有更多的请求时,Scrapy关闭,爬取过程结束。
简单使用
项目命令
- 创建项目:
- 使用命令
scrapy startproject <project_name> [project_dir]
创建新项目。其中<project_name>
是必填项,表示项目名称;[project_dir]
是可选项,表示项目目录。 - 例如:
scrapy startproject db
会创建一个名为 ‘db’ 的Scrapy项目。
- 使用命令
- 创建第一个爬虫:
- 首先,需要进入到项目目录中,使用
cd <project_name>
命令。 - 然后,使用
scrapy genspider <name> <domain>
创建一个新的爬虫。这里<name>
是爬虫的名称,而<domain>
是爬虫将要爬取的域名。 - 例如:
scrapy genspider example example.com
会在项目的spiders
目录下创建一个名为 ‘example’ 的爬虫,它针对的是 ‘example.com’ 网站。
- 首先,需要进入到项目目录中,使用
- 运行项目:
- 使用命令
scrapy crawl <spider_name>
来运行爬虫。这里<spider_name>
是在创建爬虫时指定的名称。 - 例如:
scrapy crawl example
会运行名为 ‘example’ 的爬虫。
- 使用命令
- 设置配置文件:
- 在
settings.py
文件中,可以配置各种参数,比如ROBOTSTXT_OBEY
(是否遵守网站的robots.txt规则)和DEFAULT_REQUEST_HEADERS
(默认的HTTP头部)。
- 在
警告
由于可以设置多个爬虫,且通过
name
进行管理,因此scrapy genspider <name> <domain>
创建新爬虫的时候的时候name
是不能重复的,会报错无数据可以查看是否限制了域名
dbtest1.py
:1
2
3
4
5
6
7
8
9
10import scrapy
class Test1Spider(scrapy.Spider):
name = "test1" #爬虫名
allowed_domains = ["movie.douban.com"] #限制域名,可以添加多个域名(列表)
start_urls = ["https://movie.douban.com/top250"] #初始页面,第一个抓取的页面,放在产生请求的地方也就是在爬虫部件
def parse(self, response): #response就是上面start_urls的响应
pass
项目文件介绍
爬取豆瓣电影
项目目的和要求
目标是创建一个 Scrapy 爬虫来爬取豆瓣电影的前 250 个电影信息。这些信息包括:
- 电影名称
- 导演和演员信息
- 电影评分
需要将这些信息保存到本地,同时通过 Scrapy 的管道(Pipelines)机制进行持久化。
项目搭建
创建 Spider 类
首先需要创建一个名为 Test1Spider
的类,该类继承自 scrapy.Spider
。在这个类中,定义以下属性和方法:
name
: 爬虫的唯一标识名start_urls
: 包含开始爬取的 URL 列表parse(self, response)
: 是爬虫的核心方法,用于处理响应并返回提取的数据或新的请求。
创建命令示例:
scrapy genspider dbtest1 movie.douban.com
这将在 spiders
目录下生成 dbtest1.py
文件。
解析响应(Parsing Response)
在 parse
方法中,使用 response
对象进行页面内容的解析。可以利用 xpath
或 css
选择器提取所需的数据。
首先分析该页面结构:
假设要获取标题,导演和分数等信息,通过分析可以发现
class="info"
即可包含所有得信息因此修改
response
直接使用xpath
语法dbtest1.py
:1
2
3
4
5
6
7
8
9class Dbtest1Spider(scrapy.Spider):
name = "dbtest1"
allowed_domains = ["movie.douban.com"]
start_urls = ["https://movie.douban.com/top250"] #修改为开始爬取的页面
# parse 里面我们只需要关注 怎么解析就行, 因为scrapy会自动帮我们去请求指定的网址
def parse(self, response):
# 这个地方可以直接使用response 就可以了,response对象里面就已经绑定了lxml的一些方法
node_list = response.xpath('//div[@class="info"]')
调试前置
可以在同目录下添加调试文件db_debug.py
,方便对爬虫文件进行断点调试
db_debug.py
:
1 | from scrapy.cmdline import execute |
通过调试可以发现传输了25行数据
清洗数据
对拿到的info
标签进行解析,利用循环进行即可
首先查看标题标签
然后通过其结构利用xpath锁定其位置
dbtest1.py
:
1 | import scrapy |
信息
在XPath中,./
和//
是两种不同的路径选择符,它们用于指定当前节点的相对路径。理解它们之间的差异对于正确地从HTML文档中提取数据是很重要的。
./
- 当前节点下的直接子节点:./
用于选择当前节点的直接子节点。- 当使用
./
时,它会限制搜索范围到直接下级的元素。 - 在例子中,
./div
意味着选择当前节点(在这里是每个div[@class="info"]
)的直接子div
元素。
//
- 文档中任何位置的节点://
用于选择文档中任何位置的节点,而不考虑它们在文档中的位置。- 使用
//
时,它会从整个文档中搜索符合条件的节点,而不仅限于当前节点的子节点。 - 在例子中,如果使用
//div
,它将会查找整个文档中所有的div
元素,而不仅仅是div[@class="info"]
下的div
元素。
使用了./div
,这意味着想要选择每个div[@class="info"]
元素的直接子div
元素。这是一种更精确的选择方式,确保只选取特定父元素下的子元素,避免从整个文档中获取不相关的div
。
调试1
断点调试后发现其获取的是对象,这是因为在Scrapy框架中,使用.xpath()
方法返回的是一个SelectorList
对象,而不是直接返回数据。SelectorList
是Scrapy中的一个特殊类型,用于表示一组通过XPath选择器找到的元素。
SelectorList
对象:- 当调用
.xpath()
方法时,返回的是一个SelectorList
对象,而不是实际的数据。 SelectorList
是一个包含多个Selector
对象的列表。每个Selector
对象代表一个通过XPath表达式匹配到的节点。
- 当调用
- 使用
.get()
方法提取数据:- 为了从
Selector
或SelectorList
对象中提取数据,需要使用.get()
方法(在早期版本的Scrapy中,这个方法叫做.extract_first()
)。 .get()
方法从SelectorList
中提取第一个匹配的元素的数据。如果没有找到匹配的元素,它将返回None
。- 如果想要提取所有匹配的元素,可以使用
.getall()
方法(旧版本中对应.extract()
)。
- 为了从
- 为什么不直接返回数据:
- 这种设计允许在决定如何处理匹配到的元素之前先检查它们。例如,可以检查
SelectorList
是否为空,或者提取它包含的所有元素。 - 它还提供了更大的灵活性,例如先对匹配到的元素进行进一步的选择或应用额外的XPath表达式。
- 这种设计允许在决定如何处理匹配到的元素之前先检查它们。例如,可以检查
因此,要从中提取文本数据,需要调用 .get()
方法。
调试2
随后继续调试可以发现工作人员部分,带有额外空白字符
而使用 .strip()
方法在处理从网页提取的文本数据时是非常常见且有用的,特别是当文本包含了不必要的空白字符(如空格、制表符、换行符等)时。.strip()
方法的作用是移除字符串两端的空白字符,使得提取的数据更加整洁和一致。
原因和原理:
- 移除多余空白:
- 网页中的文本经常包含了在HTML代码中用于格式化的额外空白字符。这些字符在网页上看不出来,但在提取文本数据时会被包含进来。
.strip()
方法用于移除这些不需要的空白字符,如前后的空格和换行符,这对于清洁数据和后续处理非常有帮助。
- 格式化和一致性:
- 使用
.strip()
可以确保数据的格式一致,无论原始HTML中的格式如何。 - 它有助于减少后续处理数据时的麻烦,特别是在比较、存储或展示数据时。
- 使用
- 使用方法:
- 在Python中,
.strip()
方法无需任何参数即可移除字符串两端的空白字符。 - 如果想要移除其他特定字符,也可以传递一个字符串作为参数给
.strip()
,它将移除字符串两端的任何包含在该参数中的字符。
- 在Python中,
这样,employee
将不再包含字符串开头和结尾的空格或换行符,使得数据更加整洁和便于处理。
调试3
最后对分数进行调试,发现没有返回数据,而这是因为搜索节点的范围和深度的缘故造成的
- 当使用
./span[@class="rating_num"]/text()
:- 这里的
./
将会导致XPath表达式只在当前处理节点的直接子节点中进行搜索。如果当前处理节点是div
元素的父节点,那么./div[@class="star"]
将会选中直接子节点中的class="star"
的div
元素。然后,/span[@class="rating_num"]/text()
将会从这个选中的div
元素中选择class="rating_num"
的span
元素,并提取其文本内容。 - 然而,由于当前处理的节点是
div[@class="star"]
内部的一个节点,或者更深层次的一个节点,因此./
就不能正确地选中所需的元素了,因为它不会搜索更深层次的节点。 - 所以会返回
None
- 这里的
- 当使用
.//span[@class="rating_num"]/text()
:.//
表示从当前节点开始查找,不限于直接子节点,而是包括所有后代节点。div[@class="star"]
表示选择class
属性为 “star” 的div
元素。span[@class="rating_num"]
表示选择class
属性为 “rating_num” 的span
元素。/text()
表示获取选定span
元素的文本内容。- 由于
span
元素嵌套在class="star"
的div
元素内部,所以需要使用.//
来确保能够选中正确的元素,无论它位于何种深度
在XPath中,./
和 .//
的差异主要在于它们搜索节点的范围和深度。这个差别对于定位和提取网页中的数据是至关重要的。
./
- 直接子节点:./
表示选择当前节点的直接子节点。- 当使用
./
时,它将只在当前节点的下一级(即直接子节点)中查找匹配的元素。 - 例如,如果当前节点是一个
div
元素,./span
将只会选择该div
元素的直接子span
元素。
.//
- 当前节点及其所有后代节点:.//
表示在当前节点及其所有后代(子节点、孙节点等)中选择节点。- 当使用
.//
时,它会搜索整个子树,找到所有匹配的元素,无论它们位于当前节点的哪个层级。 - 所以,如果当前节点是一个
div
元素,.//span
将会选择该div
及其所有子节点中的所有span
元素,无论这些span
元素嵌套得有多深。
在实际应用中,如果不确定当前处理的节点在HTML结构中的确切位置,使用 .//
会更加安全,因为它不受节点深度的限制。而 ./
更适合在已知确切的层次结构和上下文中使用。
dbtest1.py
:
1 | import scrapy |
定义 Item
- Scrapy使用Item类来定义数据结构。当从非结构化数据源(如网页)提取数据时,使用Item对象可以确保数据结构化和一致性。
- Item对象类似于Python字典,但提供了额外的结构化和错误检测功能。这在大型项目中尤其重要,因为它减少了字段名错误和数据不一致的风险。
- 在Scrapy项目中,通常需要在
items.py
文件中定义Item类,然后在爬虫代码中导入和使用这些类。
Scrapy 使用 Item
类来定义结构化数据字段,它类似于 Python 的字典但提供额外的保护和便利。在 items.py
文件中定义数据结构。
dbtest1.py
:
1 | import scrapy |
只会存储item.py
里定义的字段
items.py
:
1 | # Define here the models for your scraped items |
信息
在Python中,点(.
)和双点(..
)是相对导入的一部分,它们表示当前和父包的目录位置。
- 单个点(
.
)表示当前模块的目录位置。 - 双点(
..
)表示父目录的位置。
当在一个Python模块中看到 from ..items import DbItem
这样的语句时,这意味着:
from ..items
表示从当前模块的父包中的items
模块导入。import DbItem
是指导入items
模块中定义的DbItem
类。
这种导入方式通常用在一个包结构中,使得模块可以导入同一包内或父包中的其他模块。例如,在Scrapy项目中,可能有以下的目录结构:
1 | myproject/ |
如果在 spider1.py
或 spider2.py
中需要导入 items.py
中定义的 DbItem
类,需要使用相对导入。由于 spiders
是一个子包,需要使用 ..
来表示 items.py
所在的父包 myproject
。
使用相对导入的好处是,它不依赖于包的具体位置,这使得整个包更易于重构和移动。然而,相对导入也要小心使用,因为它们只能用在一个包的内部,不能用于顶级模块,否则会引发 ImportError
。
信息
在Scrapy框架中,yield
关键字用于从一个函数返回数据项,但与return
不同,yield
实现的是一种称为生成器的概念,允许函数在保持其状态的情况下产生一个序列的值,这使得函数能够在每次产生一个值后暂停执行,并在下一次从它停止的地方继续执行。
在Scrapy中,yield
通常在爬虫的parse
方法中使用,用来产生以下几种可能的对象:
- Item对象: 当爬虫解析网页并提取数据时,它会创建一个Item对象,并填充数据。然后通过
yield
语句返回这个Item对象,Scrapy引擎会将这个Item传递给Item Pipeline进行进一步的处理,如清洗、验证和存储。 - Request对象: 如果页面需要跟进链接或翻页,爬虫会创建一个Request对象,并通过
yield
返回。这个Request对象包含了请求的URL和一个回调函数,Scrapy会排队发送这个请求,并在收到响应后将其传递给指定的回调函数。
使用yield
的优势在于它的内存效率和执行效率。生成器不需要在处理大量数据时将它们全部存储在内存中,它们只在需要时产生一个数据项。对于大规模的爬虫任务,这意味着更低的内存消耗和更好的性能。
下面是使用yield
在Scrapy爬虫中返回Item对象的示例代码:
1 | def parse(self, response): |
在这个例子中,每次循环都会提取数据,创建一个Item对象,并通过yield
返回它。Scrapy引擎接收到这些Item后,会将它们发送到Item Pipeline。
追踪链接(Following Links)
- 为了实现多页爬取,需要在爬虫中实现链接追踪。这通常涉及从当前页面提取下一页的链接或根据一定的规则构建新的URL。
- 可以在爬虫中使用类变量来追踪当前页码,并在解析函数中构造下一页的URL。如果没有更多的页面可以爬取,爬虫将停止。
要爬取所有250部电影的信息,需要从初始页面提取到其他页面的链接,并对其进行跟踪。这可以通过构造新的 scrapy.Request
对象并从 parse
方法返回实现
定义和使用管道(Pipelines)
- Item管道是Scrapy中用于处理爬虫返回的Item对象的组件。它们可以执行多种操作,如清理HTML数据、验证数据完整性、检查重复项和进行数据持久化。
- 创建管道组件通常涉及编写一个Python类,并在该类中实现特定的方法来处理Item对象。
- 为了激活特定的管道组件,需要在Scrapy的
settings.py
文件中的ITEM_PIPELINES
设置中添加并配置它们。
管道用于处理 Spider
从网页中提取的 Item。常见的用途包括清洗数据、去重、存储数据到文件或数据库中。
- 在
pipelines.py
中定义一个管道类,用于处理Item
对象。 - 在
settings.py
中启用管道(ITEM_PIPELINES
设置)。
1 | # Define your item pipelines here |
存储数据
- 可以编写管道类来实现特定的数据处理需求,例如将爬取的数据保存到文件中。
- 管道的执行顺序是根据在
settings.py
中分配给它们的整数值确定的。
修改 pipelines.py
文件以添加数据存储逻辑。例如,将数据写入到本地的 film.txt
文件中。
运行爬虫
进入项目根目录,运行以下命令以启动爬虫:
scrapy crawl db
注意
Robots.txt 协议: 默认情况下,Scrapy 会遵守 Robots.txt 协议。如果需要爬取被此协议禁止的内容,需在
settings.py
中将ROBOTSTXT_OBEY
设置为False
。表头设置(伪装): 有些网站会根据请求的 User-Agent 进行内容过滤。如果需要,可在
settings.py
中设置自定义的表头。管道激活: 需要在
settings.py
中激活管道,以便将提取的数据传递到pipelines.py
。300
是一个整数值,定义了Pipeline的执行顺序,数字越小越早执行。Item 字段定义: 在
items.py
中定义字段,这些字段决定了可以从爬取的页面中提取哪些数据。
Scrapy shell
scrapy shell
是一个强大的交互式环境,它允许开发者在不运行整个爬虫的情况下测试和调试Scrapy的数据抓取代码。它可以加载网页,执行选择器,甚至可以运行爬虫的解析函数,是Scrapy开发中不可或缺的调试工具。
在项目目录下输入 scrapy shell https://movie.douban.com/top250
会启动Scrapy shell并预加载豆瓣电影Top 250的页面进行调试。下面是对Scrapy shell中提供的一些快捷方法和对象的详细说明:
快捷方法
shelp()
:- 显示Scrapy shell可用的快捷方法和支持的对象的帮助文档。
fetch(url[,redirect=True])
:- 用于在Scrapy shell中获取指定网址的响应。如果
redirect
为True
,则会跟随重定向。
- 用于在Scrapy shell中获取指定网址的响应。如果
fetch(request)
:- 与上面的方法相似,但这里可以传递一个Scrapy的
Request
对象,而不是URL字符串。
- 与上面的方法相似,但这里可以传递一个Scrapy的
view(response)
:- 在本地浏览器中打开当前响应的页面,便于直观地查看Scrapy获取的数据与实际浏览器中呈现的网页之间的差异。
Scrapy 对象
crawler
:- 表示当前运行的爬虫的
Crawler
对象,包含了很多与爬虫运行相关的信息和接口。
- 表示当前运行的爬虫的
spider
:- 当前被加载的
Spider
对象。如果在shell中加载了一个特定的页面,Scrapy会尝试为这个域创建一个爬虫对象。
- 当前被加载的
request
:- 当前被执行的
Request
对象。在执行fetch()
方法后,可以通过这个对象查看请求的具体信息。
- 当前被执行的
response
:- 最近一次执行
fetch()
方法后返回的Response
对象。可以通过它来访问页面的内容,并使用选择器提取数据。
- 最近一次执行
settings
:- 当前Scrapy项目的设置。可以通过它来查看或修改Scrapy的配置信息。
在Scrapy shell中,也可以使用所有的Python标准库和Scrapy提供的选择器,例如response.xpath()
或response.css()
来测试和调试数据提取的代码。
总结起来,scrapy shell
提供了一个沙箱环境,让开发者可以实时测试XPath或CSS选择器,查看响应内容,甚至是测试item pipelines的代码,这样可以在不必运行整个Scrapy项目的情况下快速调试和修正代码中的问题。
示例
使用IPython 作为控制台的后端在终端(Terminal)中运行 Scrapy shell,Scrapy 会自动检测到 IPython 并使用它,相较于标准 Python 解释器这样操作更加简便。
在项目文件目录下成功启动Scrapy shell后输入settings['DEFAULT_REQUEST_HEADERS']
即可查看settings
文件中的DEFAULT_REQUEST_HEADERS
的默认配置信息
同样的可以查看当前的爬虫和爬虫类的属性值
例如spider.name
或者利用fetch()
进行URL的切换
但是个人觉得还是用IDE的调试方便,但是一般对于已经部署在服务器的项目还是使用Shell才行,毕竟不用修改代码
Scrapy Selector 类
选择器 Selector 是 Scrapy 的核心部分,用于提取 HTML/XML 文档中的数据。
Scrapy 和 lxml 的关系:
- Scrapy 默认使用
lxml
作为其解析器,这意味着即使没有直接导入lxml
,Scrapy 仍然依赖于它来解析 HTML/XML。 lxml
提供了底层的解析功能,而 Scrapy 的选择器在此基础上提供了更易用的接口。
- Scrapy 默认使用
选择器的主要方法:
xpath()
: 接受 XPath 表达式,用于选择 HTML/XML 文档中的元素。css()
: 使用 CSS 选择器来选择元素。- 这些方法返回的是选择器列表(SelectorList 对象),可以进一步用于提取或操作数据。
提取数据的方法:
extract()
: 返回所有匹配的元素的数据。extract_first()
: 返回第一个匹配元素的数据,如果没有匹配元素则返回None
。get()
和getall()
: 它们是extract_first()
和extract()
的别名。
嵌套选择器的使用:
- 在某些情况下,可能需要多次调用
.xpath()
或.css()
来精确定位数据。 - 示例:
response.css('img').xpath('@src')
会首先选择所有<img>
标签,然后提取它们的src
属性。
- 在某些情况下,可能需要多次调用
使用正则表达式提取数据:
.re()
: 使用正则表达式提取数据,返回匹配的字符串列表。.re_first()
: 返回第一个匹配的字符串。
示例 HTML 字符串:
- 提供的 HTML 字符串
html_str
是一个豆瓣电影页面的一部分,包含电影信息如标题、导演、评分等。 - 使用 Scrapy 选择器,可以提取这些信息,如电影名称、评分等。
- 提供的 HTML 字符串
1 | html_str=""" |
- 选择器使用的完整示例:
1 | from scrapy.selector import Selector |
Selector 类的基础用法
1 | from scrapy.selector import Selector |
导入 Selector
类和 HtmlResponse
类的语句。
Selector
类:Selector
类用于从 HTML 或 XML 文档中提取数据。- 它提供了使用 XPath 和 CSS 选择器查询文档的功能。
- 通过
Selector
类,可以轻松地提取网页中的特定部分,如文本、链接、标签属性等。
HtmlResponse
类:HtmlResponse
类是 Scrapy 用于表示 HTTP 响应的一个类。- 它是
Response
类的一个子类,专门用于处理 HTML 类型的内容。 - 通过
HtmlResponse
,可以访问响应的 URL、状态码、头部(headers)、正文(body)等信息。 - 它通常与
Request
对象一起使用,表示发送请求后收到的 HTTP 响应。
这两个类在编写 Scrapy 爬虫时非常有用。例如,当发送一个 HTTP 请求并收到响应时,可以使用 HtmlResponse
对象来处理这个响应,并使用 Selector
对象来解析和提取所需的数据。
1. 通过文本构造 Selector 实例
1 | select_txt = Selector(text=html_str) |
Selector
是 Scrapy 框架中的一个核心类,用于执行对 HTML 或 XML 文档的选择和提取操作。- 这行代码创建了一个
Selector
的实例,命名为select_txt
。 text=html_str
:这里text
参数用于传递 HTML 内容。html_str
应该是一个包含 HTML 文档内容的字符串。在实际应用中,这个字符串通常是从网页请求中获取的 HTML 响应体。- 通过传递这个 HTML 字符串,
Selector
类的实例select_txt
将能够解析这个 HTML 文档。
- 创建了
select_txt
实例后,可以使用它来进行数据提取。 Selector
提供了多种方法来查询和提取 HTML 文档中的数据,最常见的包括 XPath 和 CSS 查询。- 例如,
select_txt.xpath('//div')
会选择 HTML 文档中所有的<div>
元素。 - 另一个例子,
select_txt.css('div.classname')
会选择所有 class 属性为classname
的<div>
元素。
- 例如,
- 这行代码的主要功能是创建一个 Selector 对象,用于解析和操作给定的 HTML 字符串。这是 Scrapy 爬虫开发中常见的模式,用于从网页中提取信息,如提取链接、文本内容、图像地址等。通过这种方式,Scrapy 能够高效地处理和解析网页内容。
提取电影名称
1 | select_txt.xpath('//div[@class="info"]/div/a/span/text()').get() |
select_txt
是一个Selector
对象,它包含了 HTML 文档的内容。.xpath('//div[@class="info"]/div/a/span/text()')
是一个 XPath 查询。这个查询的含义如下://div[@class="info"]
:这部分选择所有具有class="info"
属性的<div>
元素。//
表示在整个文档中查找,而[@class="info"]
是一个条件,用于筛选具有指定类名的<div>
元素。/div/a/span
:对于每个符合上述条件的<div>
元素,进一步选择其内部的子<div>
,然后选择这些<div>
内部的<a>
元素,再选择这些<a>
元素内部的<span>
元素。这是一个层层深入的选择过程。/text()
:最后,这部分选择了前面找到的<span>
元素中的文本内容。text()
函数用于获取一个元素的文本部分。
.get()
:这个方法用于从上面的 XPath 查询中提取第一个匹配元素的文本内容。如果查询没有找到匹配的元素,则返回None
。
提取所有匹配元素的文本列表
1 | select_txt.xpath('//div[@class="info"]/div/a/span/text()').getall() |
- 类似于前面的查询,但使用
getall()
方法来获取所有匹配元素的文本内容列表。 .getall()
:这个方法从 XPath 查询中提取所有匹配元素的文本内容,并返回一个列表。如果没有找到匹配的元素,则返回一个空列表。
使用 extract_first() 提取第一个匹配元素的文本
1 | select_txt.xpath('//div[@class="info"]/div/a/span/text()').extract_first() |
extract_first()
方法的功能与get()
相似,用于提取第一个匹配元素的文本。
使用 extract() 提取所有匹配元素的文本列表
1 | select_txt.xpath('//div[@class="info"]/div/a/span/text()').extract() |
extract()
方法的功能与getall()
相似,用于提取所有匹配元素的文本内容列表。
提取特定子元素的文本
1 | select_txt.xpath('//div[@class="info"]/div/a/span[1]/text()').getall()[0] |
select_txt
应该是一个Selector
对象,包含了 HTML 文档的内容。.xpath('//div[@class="info"]/div/a/span[1]/text()')
是一个 XPath 查询。//div[@class="info"]
:选择所有具有class="info"
属性的<div>
元素。//
表示在整个文档中查找。/div/a/span[1]
:对于每个符合上述条件的<div>
元素,进一步选择其内部的子<div>
,然后选择这些<div>
内部的<a>
元素,再选择这些<a>
元素内部的第一个<span>
元素(由[1]
指定)。/text()
:选择前面找到的<span>
元素中的文本内容。
.getall()
:这个方法用于从 XPath 查询中提取所有匹配元素的文本内容,并返回一个列表。[0]
:这个索引用于从列表中获取第一个元素。在 Python 中,列表的索引从 0 开始。
2. 通过 response 构造 Selector 实例
1 | # 创建 HtmlResponse 对象 |
HtmlResponse
是 Scrapy 框架中用来表示 HTTP 响应的一个类。url='http://www.example.com/'
:这里指定了响应所对应的 URL。在实际的 Scrapy 应用中,这通常是发起请求的目标 URL。body=html_str.encode():body
参数用于提供 HTTP 响应的正文内容。
html_str
应该是一个包含 HTML 内容的字符串。.encode()
方法将这个字符串转换为字节序列。在 HTTP 通信中,正文内容通常以字节形式存在,所以需要进行这样的转换。
Selector
类在 Scrapy 中用于选择和提取 HTML 或 XML 文档中的数据。response=response
:这里将前面创建的HtmlResponse
对象传递给Selector
。这样,Selector
就可以使用HtmlResponse
中的 HTML 内容进行解析和数据提取。select_tet
是创建的Selector
对象的实例,它现在包含了response
中的 HTML 数据,并可以使用该对象进行各种选择器查询(如 XPath 或 CSS 查询)。这两行代码组合使用
HtmlResponse
和Selector
类来处理和解析 HTML 数据。首先,使用HtmlResponse
来模拟一个 HTTP 响应,包括它的 URL 和正文内容。然后,创建一个Selector
对象来解析这个响应的 HTML 内容,并提供数据提取的功能。这是在 Scrapy 爬虫中常见的模式,用于从网页中提取所需的信息。
使用 Selector 实例和 response 的 selector 属性提取数据
1 | select_tet.xpath('//div[@class="info"]/div/a/span/text()').get() |
select_tet
应该是一个由Selector
类实例化的对象。这个对象包含了某个 HTML 文档的内容。.xpath('//div[@class="info"]/div/a/span/text()')
是一个 XPath 选择器表达式。这个表达式的作用是:
//div[@class="info"]
:选择所有具有class="info"
属性的<div>
标签。/div/a/span
:在每个这样的<div>
标签内,进一步选择嵌套的<div>
,然后选择其内部的<a>
标签,再选择<a>
标签内的<span>
标签。/text()
:提取这些<span>
标签内的文本内容。
.get()
:这个方法用于获取匹配的第一个元素的文本内容。如果没有找到匹配的元素,将返回None
。response
应该是一个HtmlResponse
对象,通常在 Scrapy 中表示对某个 URL 请求的响应。response.selector
是HtmlResponse
对象的属性,它提供了一个Selector
对象,用于在响应的 HTML 内容上进行选择器查询。.xpath('//div[@class="info"]/div/a/span/text()')
和第一行代码中的 XPath 表达式相同,作用也相同,用于选择特定结构的元素并提取其文本内容。.get()
的作用同上,用于获取匹配的第一个元素的文本内容。这两行代码实现的功能是相同的:从 HTML 文档中提取具有特定结构的元素(在这种情况下是某些
<span>
标签内的文本内容)。区别在于它们的选择器来源不同 — 第一行代码使用的是直接从 HTML 文本创建的选择器,而第二行代码使用的是从HtmlResponse
对象创建的选择器。这种灵活性使得 Scrapy 可以适应不同的数据提取场景。
结合 CSS 和 XPath 选择器及正则表达式提取数据
1 | select_tet.css('a').xpath('./span/text()').re_first('.*的') |
select_tet.css('a')
:select_tet
应该是一个Selector
对象,它包含了从网页中提取的 HTML 数据。.css('a')
是一个 CSS 选择器,用来选取所有的<a>
标签。在 Scrapy 中,css
方法用于执行 CSS 选择器查询。- 这一步的结果是一个新的
SelectorList
对象,包含了 HTML 文档中所有的<a>
标签。
.xpath('./span/text()')
:.xpath()
是一个 XPath 选择器方法,用来进一步从前一步的结果中筛选数据。'./span/text()'
是一个 XPath 查询,它的含义是:在当前节点(这里指的是每个<a>
标签)的基础上,选择其子节点中的<span>
标签,并获取这些<span>
标签的文本内容。- 这一步的结果是一个
SelectorList
对象,包含了所有选中<span>
标签的文本内容。
.re_first('.\*的')
:.re_first()
是一个正则表达式方法,用于在之前选择的文本中进行模式匹配。'.*的'
是一个正则表达式,.
表示任意字符,*
表示零次或多次重复,的
是要匹配的具体字符。因此,这个表达式的意思是匹配任何以 “的” 结尾的字符串。re_first
表示只提取第一个匹配的结果。如果没有找到匹配项,则返回None
。
在 HTML 文档中查找所有 <a>
标签,然后在每个 <a>
标签中查找 <span>
标签的文本内容,并从这些文本中提取第一个以 “的” 结尾的字符串。这种方法在处理 HTML 数据时非常灵活,可以有效地结合不同的选择器和正则表达式来提取复杂的数据结构。
Scrapy Spider 类
Spider的名称 (
name
):- 这是一个字符串,用于定义此蜘蛛的名称。
- 名称必须唯一,因为它是Scrapy定位和实例化蜘蛛的方式。
- 这是最重要的蜘蛛属性,必须提供。
起始URLs (
start_urls
):- 这是蜘蛛开始爬取的URL列表。
- 爬虫的第一页下载将是此列表中的页面。
- 后续的请求将从这些起始URL中提取的数据中连续生成。
自定义设置 (
custom_settings
):- 这些设置在运行特定蜘蛛时会覆盖项目范围的全局设置。
- 必须定义为类属性,因为在实例化蜘蛛之前,设置就已经更新。
Spider 类的基础结构
1 | class Spider(object_ref): |
class Spider(object_ref)
: 这行定义了一个名为Spider
的类。它从object_ref
继承,但这里的object_ref
看起来像是一个错误或者不完整的代码,因为通常 Python 中的类是从object
类继承的。这可能是一个笔误或者特定项目中的自定义实现。- 类的文档字符串说明了这个类是所有 Scrapy Spider 的基类,意味着所有的 Scrapy 爬虫都应该从这个类继承。
name = None
: 这是一个类级别的属性,用于存储爬虫的名称。在 Scrapy 中,每个爬虫的名称应该是唯一的。custom_settings = None
: 这也是一个类级别的属性,用于定义特定于此爬虫的自定义设置,这些设置将覆盖全局设置。def __init__(self, name=None, **kwargs)
: 这是 Spider 类的构造函数。它接受一个可选的name
参数和任意数量的关键字参数(**kwargs
)。- 这部分代码用于设置爬虫的名称。如果构造函数中提供了
name
,它将被用作爬虫的名称。 - 如果没有提供
name
并且类的name
属性也没有被设置,会抛出一个ValueError
异常,因为每个爬虫必须有一个唯一的名称。 self.__dict__.update(kwargs)
: 这行代码将所有通过**kwargs
传入的关键字参数添加到类的实例字典中。这允许在创建爬虫实例时传入额外的属性或设置。if not hasattr(self, 'start_urls')
: 这里检查实例是否有start_urls
属性,如果没有,则初始化为空列表。start_urls
是爬虫开始爬取的 URL 列表。- 这个类是 Scrapy 爬虫的一个基本框架,提供了命名、自定义设置和初始化的基本机制。任何具体的 Scrapy 爬虫都应该继承这个类,并根据需要提供具体的实现细节。
日志系统 Logger
在 Scrapy 的 Spider 类中,日志系统被用于记录信息、警告、错误等。这对于监控爬虫的行为、调试和记录重要事件非常有用。
Logger 属性
1 |
|
@property
:这是一个 Python 装饰器,用于将一个方法转换为属性。这意味着可以通过self.logger
访问这个方法返回的值,而不是self.logger()
。logger = logging.getLogger(self.name)
:这里创建了一个日志记录器(logger)。getLogger(self.name)
使用爬虫的名称(self.name
)获取一个日志记录器实例。如果不存在具有该名称的记录器,将自动创建一个。return logging.LoggerAdapter(logger, {'spider': self})
:返回一个LoggerAdapter
实例。这是一个对标准日志记录器的包装,它提供了额外的上下文信息,使得每个日志消息都能够知道是由哪个爬虫实例产生的。这里的上下文信息是{'spider': self}
,即当前的爬虫实例。
Log 方法
1 | def log(self, message, level=logging.DEBUG, **kw): |
def log(self, message, level=logging.DEBUG, **kw)
: 这是一个辅助方法,用于在爬虫内部发送日志消息。level=logging.DEBUG
:默认的日志级别设置为 DEBUG。日志级别决定了记录的消息类型。常见的级别包括 DEBUG, INFO, WARNING, ERROR, 和 CRITICAL。self.logger.log(level, message, **kw)
:这个调用使用了之前定义的logger
属性。它将消息message
记录到日志中,级别为level
。**kw
可以传递额外的关键字参数。
使用示例
1 | class MySpider(scrapy.Spider): |
在这个例子中,每当 parse
方法被调用时,都会记录一个包含响应 URL 的信息级别日志。
from_crawler 和 _set_crawler 方法
Spider 类的 from_crawler
类方法和 _set_crawler
实例方法。这些方法在 Scrapy 的内部机制中扮演重要角色,尤其是在爬虫的初始化过程中。
from_crawler
方法
1 |
|
@classmethod
:这是一个 Python 装饰器,用于定义一个类方法。不同于普通的实例方法,类方法接收类本身作为第一个参数(通常命名为cls
)而非类的实例。from_crawler
是 Scrapy 用来创建 Spider 实例的标准方法。它接收一个crawler
对象作为参数,以及任意数量的额外的位置和关键字参数。spider = cls(*args, **kwargs)
:这行代码使用传入的参数创建了一个 Spider 实例。spider._set_crawler(crawler)
:这行代码调用_set_crawler
方法,将crawler
对象设置到创建的 Spider 实例上。return spider
:返回创建好的 Spider 实例。
_set_crawler
方法
1 | def _set_crawler(self, crawler): |
- 这是一个内部方法,用于在 Spider 实例上设置
crawler
对象。 self.crawler = crawler
:将传入的crawler
对象赋值给 Spider 实例的crawler
属性。self.settings = crawler.settings
:将crawler
对象的设置赋值给 Spider 实例的settings
属性。这样,Spider 可以访问和使用 Scrapy 项目的设置。crawler.signals.connect(self.close, signals.spider_closed)
:这行代码连接了 Spider 的close
方法到 Scrapy 的spider_closed
信号。当爬虫关闭时,这个信号会被触发,并调用 Spider 的close
方法。
在 Scrapy 框架中,from_crawler
方法通常由框架自身调用,用于创建并初始化 Spider 对象。这个过程包括设置爬虫的配置和信号处理等。一般情况下,开发者无需覆盖这个方法,除非需要进行一些特殊的初始化操作。from_crawler
和 _set_crawler
方法是 Scrapy 框架中 Spider 类的重要组成部分,它们负责爬虫的初始化和设置。这些方法确保了每个 Spider 实例可以访问到它所需的资源和配置,同时也使得爬虫能够正确响应 Scrapy 框架的信号。
start_requests 方法
start_requests
是一个关键方法,用于生成爬虫启动时的初始请求。这个方法只会在爬虫开始时调用一次。下面是对这个方法的代码和概念的详细解释和整理:
1 | def start_requests(self): |
def start_requests(self)
: 这是 Scrapy Spider 类中的一个实例方法,用于生成初始的网页请求。cls = self.__class__
: 获取当前实例的类。if not self.start_urls and hasattr(self, 'start_url')
: 这个条件检查是否定义了start_urls
属性。如果start_urls
没有定义或为空,并且错误地定义了start_url
(缺少末尾的 ‘s’),则抛出异常。if method_is_overridden(cls, Spider, 'make_requests_from_url')
: 检查是否重写了make_requests_from_url
方法。这个方法已被弃用,Scrapy 鼓励使用start_requests
方法。warnings.warn(...)
: 如果使用了弃用的方法,显示警告信息。for url in self.start_urls
: 遍历start_urls
列表中的每个 URL。yield self.make_requests_from_url(url)
: 对于每个 URL,使用make_requests_from_url
方法生成请求。这是兼容旧代码的方式。yield Request(url, dont_filter=True)
: 对于每个 URL,创建一个 Scrapy 的Request
对象并返回。dont_filter=True
表示对这些请求不应用去重过滤器。- 通常,Scrapy 爬虫定义了
start_urls
列表,并依赖start_requests
方法来为这些 URL 创建初始请求。可以覆盖start_requests
方法以提供更复杂的启动逻辑,例如从外部数据源读取 URL,或添加特殊的请求头和元数据。 start_requests
方法是 Scrapy 爬虫的起点,负责生成爬虫的初始请求。这个方法应返回一个可迭代对象,其中包含了爬虫开始时需要处理的Request
对象。通过重写这个方法,开发者可以自定义爬虫的启动行为,例如,从外部文件读取起始 URL,或者添加特定的请求参数和头部
parse 方法
这是Scrapy在其请求未指定回调时处理下载的响应时使用的默认回调
1 | def parse(self, response): |
parse
是 Scrapy 爬虫中最重要的方法之一。它是默认的回调函数,用于处理由 Scrapy 发出的请求所返回的响应。- 当一个请求没有指定特定的回调函数时,Scrapy 会自动调用
parse
方法。 - 在这个示例中,
parse
方法被定义为抛出NotImplementedError
异常。这是一个通常的做法,用来提示开发者应该在自己的爬虫类中覆盖这个方法,提供具体的实现逻辑。 - 实际使用中,
parse
方法通常包含解析响应(response
对象)并提取数据或进一步生成请求(Request
对象)的逻辑。 parse
是处理响应的默认方法
close 方法
close
方法在爬虫关闭时被调用。它是一个钩子(hook),提供了一种机会在爬虫结束时执行某些操作,比如清理资源、保存状态、发出通知等。close
方法的具体实现会根据爬虫的需求而有所不同。在 Scrapy 框架的默认实现中,这个方法可能并未显式地定义,但可以根据需要在自定义爬虫中覆盖它。- 在开发 Scrapy 爬虫时,通常需要实现
parse
方法来定义如何处理响应。这可能包括解析 HTML、处理数据和生成新的请求。 close
方法则用于定义在爬虫关闭时需要执行的任何清理或结束任务。close
提供了在爬虫结束时执行操作的机会。
次级页面抓取及数据传递拼接
次级页面抓取
在 Scrapy 爬虫中,处理多级页面通常涉及以下步骤:
一级页面(列表页)
- 在列表页,会解析出指向详情页的链接,并对每个链接发起请求以进入二级页面。
二级页面(详情页)
- 在详情页,会提取所需的具体数据。
这个过程通常在 parse
方法(处理列表页)和一个自定义的回调方法(处理详情页)中实现。
详情页抓取
1 | def get_detail(self, response): |
get_detail
是一个自定义的回调方法,用于处理从列表页提取的详情页链接的响应。- 这个方法中,编写解析详情页的逻辑,例如提取特定的数据。
数据提取示例
- 第一种方式:
'//div[@id="link-report"]/span[@property="v:summary"]'
。这个 XPath 查询用于提取比较简单的结构的数据。 - 第二种方式:
'//div[@id="link-report"]/span[@class="all hidden"]/text()|//div[@id="link-report"]/span[@property="v:summary"]/text()'
。这个查询适用于当数据结构更复杂或有多种可能的格式时。它使用了 XPath 的联合操作符|
来匹配多个可能的路径。
参数的传递拼接
- 在 Scrapy 中,
meta
参数用于在不同的请求之间传递数据。 - 当从列表页发起对详情页的请求时,可以使用
meta
参数携带需要在详情页中使用的数据。例如,可能想传递从列表页提取的某些上下文信息到详情页。
使用 meta
的示例
1 | def parse(self, response): |
在这个示例中,parse
方法提取了详情页的链接,并发起了一个新的请求。通过设置 meta={'item': 'some_data'}
,任何想从列表页到详情页传递的数据都可以包含在这个 meta
字典中。
在 Scrapy 爬虫中处理多级页面时,通常会在列表页解析出详情页的链接,并为这些链接发起请求。在处理这些请求的回调方法中,会提取详情页的具体数据。使用 meta
参数可以在不同请求之间传递数据,这对于保持爬虫逻辑的连贯性和传递上下文信息非常重要。
豆瓣为例子
Spider 文件
1 | import scrapy |
这段代码是一个 Scrapy 爬虫的示例,用于爬取豆瓣电影 Top 250 的相关信息。下面是对这个代码的详细分析:
爬虫定义
1 | class Db250Spider(scrapy.Spider): |
- 这个类继承自
scrapy.Spider
,是Scrapy框架中用于创建爬虫的基础类。 class Db250Spider(scrapy.Spider):
这行定义了一个名为Db250Spider
的新类,它继承自scrapy.Spider
。这意味着Db250Spider
是一个Scrapy爬虫,可以继承并使用Scrapy框架提供的所有功能和属性。name = 'db250'
这里设置了爬虫的名字为db250
。这个名称是Scrapy项目中唯一的标识符,用于在命令行中指定和运行特定的爬虫。allowed_domains = ['movie.douban.com']
allowed_domains
是一个列表,包含了爬虫允许爬取的域名。这有助于确保爬虫不会跨域爬取。在这个例子中,爬虫只会爬取movie.douban.com
域下的页面。start_urls = ['https://movie.douban.com/top250']
start_urls
是一个包含起始URL的列表。当爬虫启动时,Scrapy会自动开始从这些URL发起请求。在这个例子中,爬虫将从豆瓣电影Top 250的主页开始爬取。page_num = 0
page_num
是一个类属性,用于跟踪当前爬取的页码。这在处理分页或需要在多个页面之间导航时非常有用。在这个例子中,它被初始化为0。- 接下来在这个爬虫类中定义如
parse
等方法,以指定如何解析和处理每个请求的响应,以及如何从中提取数据或者进一步生成新的请求。例如,解析豆瓣电影Top 250页面的响应,提取电影信息,然后爬取每个电影的详细页面等。
主页解析方法 parse
1 | def parse(self, response): |
0. 方法定义
1 | def parse(self, response): |
self
是对当前对象实例(即Spider对象)的引用。response
是一个包含了HTTP响应数据的对象。它包括响应内容(通常是HTML)、URL、HTTP头等信息。parse
方法的主要功能是处理response
,提取有用信息,或者进一步生成需要跟进的URL请求。- 在这个方法中,通常会使用XPath或CSS选择器来解析响应内容(如提取数据)。
- 数据提取:可以使用
response.xpath()
或response.css()
方法来选择HTML元素,然后提取所需数据。 - 生成跟进请求:如果页面中有链接到其他页面,且想爬取那些页面的数据,可以通过
scrapy.Request
生成新的请求。 parse
方法可以返回以下类型的对象:- 字典(在Python中通常是通过yield语句生成)。
scrapy.Item
对象,这是一个更结构化的方式来管理数据。Request
对象,用于生成新的爬取请求。- 或者它们的组合。
parse
方法是Scrapy爬虫的核心,用于处理响应并从中提取信息,或者根据页面中的链接生成新的请求。
1. 解析节点列表
1 | node_list = response.xpath('//div[@class="info"]') |
- 这行代码使用 XPath 查询从响应中提取所有类属性为
"info"
的<div>
元素。 - 这些
<div>
元素包含了电影的主要信息,并存储在node_list
中。 response
:- 这通常是一个由Scrapy框架(或类似的库)提供的响应对象。
- 它代表了一个从网页请求中获得的HTTP响应。
- 这个对象包含了完整的网页数据,通常是HTML格式。
.xpath()
:- 这是一个方法,用于对
response
对象中的HTML内容执行XPath查询。 - XPath是一种在XML和HTML文档中查找信息的语言。
- 通过XPath,可以导航文档的结构,并选择需要的元素、属性等。
- 这是一个方法,用于对
'//div[@class="info"]'
:- 这是传递给
.xpath()
方法的XPath查询表达式。 - 让我们分解这个表达式:
//
: 这个符号表示选择文档中的所有匹配元素,而不仅仅是直接子元素。它在整个文档中进行搜索。div
: 这指定了想要选择的元素类型。在这种情况下,它是<div>
元素。[@class="info"]
: 这是一个条件(谓语),用来进一步细化选择。它指定只选择那些具有class="info"
属性的<div>
元素。@class
: 表示选择元素的class
属性。"info"
: 表示属性值必须精确匹配info
字符串。
- 这是传递给
- 在网页的响应内容中选择所有
class
属性值为info
的<div>
元素,并将这些元素存储在node_list
变量中。这个列表可以被进一步用于抽取数据、分析或其他处理。在网页抓取和数据提取的过程中,这是一种非常常见的做法。
2. 判断并遍历节点
1 | if node_list: |
- 首先检查
node_list
是否非空,确保有要处理的节点。 - 然后遍历这些节点,每个节点 (
node
) 代表一个电影条目。 if node_list:
- 这是一个条件语句,检查
node_list
是否非空。在Python中,空列表([]
)被认为是False
,而非空列表被认为是True
。 - 这个条件确保仅当
node_list
中有元素时,即列表不为空时,才会执行下面的代码块。这是一个良好的编程实践,可以避免在空列表上执行操作时出现错误。
- 这是一个条件语句,检查
for node in node_list:
- 这是一个
for
循环,用于遍历node_list
中的每个元素。 - 在每次迭代中,
node
变量会被赋予node_list
中的当前元素。 - 如果
node_list
是由之前提到的response.xpath('//div[@class="info"]')
表达式返回的,那么每个node
很可能是一个表示HTML<div>
元素的对象。这种对象通常提供了进一步提取数据(如文本内容、属性等)的方法。
- 这是一个
- 如果
node_list
不为空,即至少包含一个元素,那么对于列表中的每个元素(每个<div class="info">
),执行循环体中的代码。循环体的具体内容没有提供,但通常它会包含进一步处理每个node
的代码,例如提取信息、打印数据、存储结果等。
3. 提取电影信息
1 | movie_name = node.xpath('./div/a/span/text()').get() |
- 提取电影名称:使用 XPath 查询从当前节点 (
node
) 中提取电影名称。 - 提取导演信息:获取导演信息,这里使用
.strip()
来移除字符串开头和结尾的空白字符。 - 提取电影评分:获取电影评分,这里使用
.//
从当前节点的所有子孙节点中查找符合条件的节点。 movie_name = node.xpath('./div/a/span/text()').get()
- 这行代码从当前的
node
中提取电影名称。 ./div/a/span/text()
: XPath查询语句。./
表示从当前节点开始。div/a/span
定位到一个<span>
元素,这个元素是一个<a>
元素的子元素,而<a>
元素又是一个<div>
元素的子元素。/text()
选择这个<span>
元素的文本内容。
.get()
是一个方法,用来获取XPath查询结果的第一个匹配项。
- 这行代码从当前的
director = node.xpath('./div/p/text()').get().strip()
- 这行代码用来提取导演的名称。
./div/p/text()
: XPath查询语句。- 类似于前面的查询,但这次是选择一个
<p>
元素的文本内容。
- 类似于前面的查询,但这次是选择一个
.get()
同样获取第一个匹配项的文本。.strip()
是一个字符串方法,用来移除字符串首尾的空白字符(如空格、换行符等)。
score = node.xpath('.//span[@class="rating_num"]/text()').get()
- 这行代码用于提取电影评分。
.//span[@class="rating_num"]/text()
: XPath查询语句。.//
表示在当前节点及其子节点中查找。span[@class="rating_num"]
选择所有class
属性为rating_num
的<span>
元素。/text()
同样选择这些<span>
元素的文本内容。
.get()
获取第一个匹配结果。
- 这三行代码是在遍历一个包含多个节点(每个节点代表一个电影信息)的列表时,用来从每个节点中提取特定信息的标准操作。这种方式在爬虫和数据提取的过程中非常常见,特别是当处理诸如电影列表页面这样的结构化数据时。
4. 创建并填充 Scrapy Item
1 | item = DbItem() |
item = DbItem()
- 这行代码创建了一个
DbItem
的实例。通常在Scrapy项目中定义,用于表示提取的数据结构。 - 在Scrapy中,
Item
对象用于定义存储爬取数据的结构。它们类似于Python中的字典,但提供额外的保护和便利,如字段定义和类型检查。
- 这行代码创建了一个
item["movie_name"] = movie_name
- 这行代码将之前提取的
movie_name
(电影名称)赋值给item
的"movie_name"
键。 - 这意味着
DbItem
类中定义了一个字段movie_name
,用于存储电影名称。
- 这行代码将之前提取的
item["director"] = director
- 类似地,这行代码将提取的
director
(导演名称)赋值给item
的"director"
键。
- 类似地,这行代码将提取的
item["score"] = score
- 同样地,这行代码将提取的
score
(电影评分)赋值给item
的"score"
键。
- 同样地,这行代码将提取的
- 创建了一个
DbItem
实例,并填充了从网页中提取的数据:电影名称、导演和评分。在Scrapy中,这样的item
对象通常会被进一步传递到管道(pipelines),在那里可以进行如存储到数据库、进行数据清洗或其他处理的操作。使用Item
对象的好处在于它提供了结构化的数据存储方式,有助于维护代码的清晰度和可维护性。
5. 请求电影详情页
1 | detail_url = node.xpath('./div/a/@href').get() |
detail_url = node.xpath('./div/a/@href').get()
- 这行代码使用XPath选择器从当前处理的节点(
node
)中提取详情页面的链接。 ./div/a/@href
: XPath查询语句。./
表示从当前节点开始。div/a
定位到当前节点下的<div>
元素中的<a>
元素。@href
获取这个<a>
元素的href
属性,即链接地址。
.get()
方法从XPath选择器返回的结果中获取第一个匹配项,也就是详情页面的URL。
- 这行代码使用XPath选择器从当前处理的节点(
yield scrapy.Request(detail_url, callback=self.get_detail, meta={"info":item})
- 这行代码创建一个新的Scrapy请求(
Request
),用于爬取在detail_url
中找到的URL。 scrapy.Request(detail_url, ...)
创建一个新的请求对象,其目标是detail_url
。callback=self.get_detail
设置当请求得到响应时应调用的回调方法为get_detail
。这意味着,当Scrapy成功访问detail_url
并获得响应时,它将自动调用get_detail
方法来处理响应。meta={"info":item}
将一个额外的信息字典{"info": item}
传递给回调方法。这通常用于在不同请求之间传递数据。在这里,item
对象(可能包含已经提取的某些数据,如电影名、导演、评分等)被传递到详情页面的处理方法中。
- 这行代码创建一个新的Scrapy请求(
- 在Scrapy中,
yield
关键字被用于生成请求而不是直接返回它们。这允许Scrapy处理请求队列,并根据可用的资源和设置来调度这些请求。这种方法使得爬虫能够有效地管理大量的并行请求,同时避免过载目标服务器或被封禁。
信息
在编程中,特别是在异步编程和事件驱动编程中,”callback”(回调函数)是一个非常重要的概念。回调函数是传递给另一个函数或方法的函数,它在那个函数或方法执行完某些操作之后被调用。在不同的编程语言和框架中,回调函数的具体实现和使用方式可能有所不同,但其基本概念是相似的。
回调函数的基本原理
- 定义回调函数: 回调函数是定义的一个函数,它将在未来的某个时间点被调用。这个函数通常定义了在某些操作完成后应该执行的操作。
- 将回调函数作为参数传递: 将这个回调函数作为参数传递给另一个函数或方法。这通常发生在希望在那个函数完成其主要操作后执行一些额外操作的情况下。
- 异步操作或事件处理: 在进行异步操作(如网络请求、文件读写等)或处理事件(如用户输入、定时器触发等)时,回调函数特别有用。在这些情况下,不能立即得到结果,回调函数提供了一种在操作完成时得到通知并执行相关代码的方式。
回调函数的例子
在Python中,回调函数的一个常见例子是在多线程或网络请求中使用:
1 | import requests |
在这个例子中,on_success
和on_error
是回调函数。它们分别在HTTP请求成功时和出错时被调用。
在Scrapy中的回调函数
在Scrapy这类网络爬虫框架中,回调函数用于处理从网页请求中返回的响应。例如:
1 | import scrapy |
在这里,parse
方法是一个回调函数,当Scrapy框架从指定的URL接收到响应时调用。
总的来说,回调函数是一种在某个操作完成后(通常是异步操作)再执行的函数。它们在事件驱动的程序设计中尤为重要,允许程序在等待一个操作完成时继续执行其他任务,并在适当的时候处理操作结果。
6. 分页处理
1 | self.page_num += 1 |
self.page_num += 1
- 这行代码将类属性
page_num
的值增加1。这个属性用于追踪当前爬取的页数。在每次处理完一个页面后,递增page_num
以移至下一个页面。
- 这行代码将类属性
page_url = 'https://movie.douban.com/top250?start={}&filter='.format(self.page_num*25)
- 这里构造了下一个页面的URL。
'https://movie.douban.com/top250?start={}&filter='
是基本的URL格式。豆瓣电影Top 250页面的分页通过URL参数start
实现,其中start
表示列表的起始位置。.format(self.page_num*25)
用于插入计算后的起始位置。由于每页显示25部电影,那么每递增一页,start
的值应增加25。例如,第一页是从0开始(0*25
),第二页是从25开始(1*25
),依此类推。
yield scrapy.Request(page_url, callback=self.parse)
- 这行代码生成了一个新的Scrapy请求,用于爬取下一个页面。
scrapy.Request(page_url, callback=self.parse)
创建一个新的请求对象,其目标是page_url
。callback=self.parse
指定当请求得到响应时,应该调用parse
方法来处理该响应。parse
是Scrapy爬虫中的标准方法,用于处理和解析响应内容。- 使用
yield
关键字来生成请求对象,允许Scrapy根据需要来调度和处理这些请求。
- 在Scrapy中,适当地管理分页是提取多页数据的关键。代码正确地实现了递增页码并构建新页面URL的逻辑。通过在
parse
方法中重复这个过程,爬虫可以遍历并爬取整个列表的所有页面。
7. 处理结束
1 | else: |
- 如果
node_list
为空,说明没有更多的电影信息可以处理,函数返回并结束。 - 如果条件不满足(
else
部分),return
语句会被执行。在Python中,return
语句用于从函数返回。如果return
后没有任何值或表达式,它默认返回None
。在这个上下文中,它表示不再继续生成新的请求,爬虫的这个部分将停止执行。
这个 parse
方法展示了如何在 Scrapy 中处理分页和详情页的链接提取、数据抓取和传递。它有效地结合了 XPath 选择器、Scrapy Item 的使用,以及 Scrapy 的请求和响应机制。
详情页解析方法 get_detail
1 | def get_detail(self, response): |
0. 方法定义
1 | def get_detail(self, response): |
- 这是一个在 Scrapy 爬虫中定义的
get_detail
方法,它是一个回调函数,用于处理电影详情页的响应。 response
参数是 Scrapy 传递给这个方法的响应对象,包含了详情页的数据。- 这个方法定义表明
get_detail
是一个实例方法,用于处理从一个特定的请求返回的响应。这个方法接收一个参数response
,它包含了对应请求的HTTP响应。
1. 初始化和更新 Item
1 | item = DbItem() |
item = DbItem()
:创建一个DbItem
对象。DbItem
通常是一个在 Scrapy 项目中定义的 Item 类,用于结构化存储提取的数据。继承自scrapy.Item
,用于指定希望收集的数据字段。info = response.meta.get("info")
:从响应的meta
属性中获取传递过来的电影基本信息。这些信息之前在处理列表页时已经被提取并通过meta
参数传递到这个方法。这里从response.meta
中提取了之前传递的元数据。response.meta
是一个字典,用于在Scrapy的不同请求之间传递数据。在这个例子中,它被用来传递之前页面上已经抓取的数据。item.update(info)
:将获取到的电影基本信息更新到item
对象中。这行代码将从上一个页面提取的数据(存储在info
字典中)合并到新创建的item
对象中。
2. 提取电影描述信息
1 | description_list = response.xpath('//div[@id="link-report"]/span[@class="all hidden"]/text()|//div[@id="link-report"]/span[@property="v:summary"]/text()').getall() |
response.xpath()
方法用于执行XPath查询。它从响应的HTML中选择指定的元素。- XPath查询字符串有两部分,通过(并集运算符)连接:
//div[@id="link-report"]/span[@class="all hidden"]/text()
: 选取所有id
为"link-report"
的<div>
元素中,类名为"all hidden"
的<span>
子元素的文本内容。//div[@id="link-report"]/span[@property="v:summary"]/text()
: 选取所有id
为"link-report"
的<div>
元素中,property
属性为"v:summary"
的<span>
子元素的文本内容。
.getall()
方法获取所有匹配的元素的文本内容,返回一个字符串列表- 将
description_list
中的所有文本片段合并成一个单一的字符串。 [des.strip() for des in description_list]
是一个列表推导式,用于遍历description_list
中的每个元素(des
),并对每个元素应用.strip()
方法。.strip()
方法移除字符串两端的空白字符(包括空格、换行符等)。''.join([...])
方法将经过清洁的字符串列表连接成一个单一的字符串。空字符串''
作为连接符,意味着直接将文本片段连在一起,没有额外的字符插入。- 这段代码的作用是从HTML响应中提取电影描述,可能包括多个文本片段,并将它们清洁和合并为一个完整的描述字符串。这种方法在处理包含多个文本块或可选文本块(例如有些电影可能只有一种描述格式)的HTML页面时非常有用。
3. 设置描述并返回 Item
1 | item["description"] = description |
item["description"] = description
- 这行代码将之前提取并合并的描述文本(
description
)赋值给item
对象的"description"
字段。 item
是一个类似于字典的对象,用于存储提取的数据。在Scrapy中,这通常是一个继承自scrapy.Item
的类的实例,用来定义和存储爬取的数据结构。- 这里,
"description"
字段是之前定义在DbItem
(或类似的Item类)中的一个字段,用于保存电影的描述文本。
- 这行代码将之前提取并合并的描述文本(
yield item
- 使用
yield
关键字来产生item
对象,将其传递给Scrapy的管道系统(Pipelines)。 - 在Scrapy中,
yield
的使用允许框架接管并异步处理这些item对象。这些item随后会通过定义好的管道进行处理,例如进行数据清洗、验证、存储到数据库等操作。 - 这种基于生成器的方法使得Scrapy能够有效地处理大量数据,并允许多个item同时在管道中进行处理,从而提高整个爬取和数据处理的效率。
- 使用
这两行代码的作用是将提取的描述文本保存到一个item对象中,并将这个对象传递给Scrapy的后续处理流程,如管道处理。这是Scrapy爬虫中处理和传递数据的常见模式。
Items 文件
定义了一个 Scrapy Item 类,名为 DbItem
,用于在 Scrapy 爬虫中存储和组织爬取的数据。
1 | import scrapy |
import scrapy
: 导入 Scrapy 模块,这是使用 Scrapy 框架的基础。class DbItem(scrapy.Item)
: 定义了一个名为DbItem
的类,该类继承自scrapy.Item
。在 Scrapy 中,Item
类用于定义一种数据结构,使得爬取的数据可以被结构化和标准化。movie_name = scrapy.Field()
: 定义了一个字段movie_name
来存储电影的名称。使用scrapy.Field()
表示这是一个用于存储数据的字段。director = scrapy.Field()
: 定义了一个字段director
用于存储电影的导演信息。score = scrapy.Field()
: 定义了一个字段score
用于存储电影的评分。description = scrapy.Field()
: 定义了一个字段description
用于存储电影的详细描述。- 在 Scrapy 爬虫中,
Item
类被用来收集和整理爬取的数据。例如,在爬取一个电影网站时,可以使用DbItem
实例来存储每部电影的相关信息。这样的数据结构化处理使得数据的存储、输出和后续处理变得更加方便和标准化。
DbItem
类是 Scrapy 项目中用于定义和存储特定数据结构的方式。它允许爬虫以一种清晰和一致的方式处理爬取的数据。在这个例子中,DbItem
用于存储电影名称、导演、评分和描述等信息,从而使得数据在 Scrapy 爬虫的整个流程中易于处理和维护。
Pipelines 文件
定义了一个 Scrapy 的 Pipeline
类,名为 DbPipeline
,用于处理由爬虫提取的数据。在 Scrapy 中,Pipeline
用于数据的清洗、验证和存储。
1 | import json |
1. 初始化及打开文件
1 | def open_spider(self, spider): |
class DbPipeline:
定义了一个名为DbPipeline
的新类。def open_spider(self, spider)
: 这个open_spider
方法在爬虫开始时被调用。用于进行一些初始化工作。self.f = open('film.txt','w', encoding='utf-8')
: 这个方法打开一个名为film.txt
的文件用于写入(’w’模式),并且指定编码为UTF-8。这是为了确保可以正确写入包含非ASCII字符(如中文)的文本。文件对象被赋值给
self.f
,这样在这个类的其他方法中也能使用这个文件对象。
2. 处理并存储数据
1 | def process_item(self, item, spider): |
def process_item(self, item, spider)
: 这个方法会被 Scrapy 框架自动调用,每次爬虫提取出一个item
时都会执行。process_item
方法是管道处理每个item的核心方法。json_str = json.dumps(dict(item), ensure_ascii=False) + '\n'
: 将item
对象转换成 JSON 字符串。ensure_ascii=False
参数确保非 ASCII 字符(如中文)被正确处理。self.f.write(json_str)
: 将 JSON 字符串写入之前打开的文件。然后,这个JSON字符串(json_str
)被写入到前面打开的文件中。每个item占一行,因为在字符串末尾添加了换行符\n
。return item
: 返回item
,以便它能够传递到其他可能存在的 pipeline 或最终输出。
3. 关闭文件
1 | def close_spider(self, spider): |
def close_spider(self, spider)
: 在爬虫结束时被调用。当爬虫关闭时,Scrapy框架会调用close_spider
方法。self.f.close()
: 关闭打开的文件。这是一个重要的步骤,确保数据被完整写入并且文件正常关闭。这个方法关闭了文件对象self.f
。这是一个良好的实践,可以确保所有数据都被写入到文件中,并且文件正确地关闭。
在 Scrapy 项目中,Pipeline
类用于处理从爬虫传递过来的数据。在这个例子中,DbPipeline
负责将每个电影的信息以 JSON 格式保存到一个文本文件中。这样的处理方式适合于数据的持久化存储,例如保存到文件、数据库等。
DbPipeline
类演示了 Scrapy 爬虫中如何使用 pipeline 对提取的数据进行进一步的处理和存储。通过在爬虫启动和关闭时执行相关操作,并在提取数据时进行处理,pipeline 为数据的后续使用提供了一个有效的方式。
Settings 文件
settings.py
是 Scrapy 项目的配置文件,用于定义爬虫的各种设置。这些设置影响着爬虫的行为方式。
此处删除了大部分注释
1 | # -*- coding: utf-8 -*- |
基本设置
BOT_NAME = 'db'
: 定义了爬虫项目的名称,这里是'db'
。SPIDER_MODULES = ['db.spiders']
: 指定了包含 Scrapy 爬虫的模块。NEWSPIDER_MODULE = 'db.spiders'
: 指定了新爬虫的模板搜索路径。
用户代理设置
DEFAULT_REQUEST_HEADERS
: 这是默认的请求头部设置。在这里,可以设置Accept
和Accept-Language
头部,以及用户代理 (User-Agent
)。这有助于模仿常规浏览器请求,避免被目标网站识别为爬虫。ROBOTSTXT_OBEY = False
: 这个设置告诉 Scrapy 是否遵守robots.txt
规则。在这里,它被设置为False
,意味着爬虫将忽略目标网站的robots.txt
规则。
Item Pipeline 设置
ITEM_PIPELINES
: 这个设置定义了项目中启用的 Item Pipeline。在这里,DbPipeline
被设置为处理 item 的 pipeline,300
表示其优先级。
爬虫项目的目标和请求流程总结
目标数据
- 目标是爬取电影信息以及从次级页面(详情页)获取电影简介。
请求流程
- 访问一级页面:爬取电影列表页,提取电影的基本信息和指向详情页的 URL。
- 访问次级页面:访问每部电影的详情页,进一步提取电影的详细简介。
数据存储和一致性
- 由于 Scrapy 是异步的,页面响应的顺序可能与请求发送的顺序不同。因此,使用
meta
参数在请求间传递数据,保证了数据(如电影的基本信息和详细简介)之间的一致性和关联。
项目案例
分析需求
获取腾讯社会招聘的岗位信息
要获取信息的URL并不会显示存在页面中因此要通过开发者模式查看,能发现其有众多异步请求且要获取的信息就在其中
分析排前的请求,在
Fetch/XHR
下的Preview
可以发现图示请求的数据结构与页面内容近似,因此该页面的URL就应该在这个请求的标头中通过标头构造页面即可开始实施抓取数据,这个URL可以发现拼接了很多参数
分析规律
- 通过分析每页的URL,发现切换页面的规律
1 | URL_1 = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1702285787290&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=au' |
可以发现pageIndex
是不一样的,一个为1一个为2,那么后续只要利用花括号pageSize={}
传值即可实现页面跳转
同时可以发现URL都存在api
因此其都是为接口数据
爬取思路
- 找到列表页的数据的url
- 使用scrapy.Request 方法进行请求
- 解析第二步的响应,提取里面的内容
步骤
1. 创建项目
首先在终端输入 scrapy startproject tx
其中tx
代表项目名称为腾讯缩写
2. 创建爬虫
然后输入scrapy genspider tencent_data careers.tencent.com
创建爬虫tencent_data
其爬取域名为careers.tencent.com
3. 编辑爬虫
在生成tencent_data.py
的爬虫文件下继承自scrapy.Spider
命名为TencentDataSpider
的类中编辑需要构造的URL,在原本的URL内修改参数pageIndex=1
为pageIndex={}
以便分页(页面跳转),将url_data
格式化字符串中的pageIndex
参数设为1,生成第一页的URL,作为爬虫开始爬取的起始点。
1 | import scrapy |
4. 处理列表列
在parse
方法中添加一个循环以确定要爬取的页面数量,可以通过循环for
来进行。parse
是Scrapy爬虫中默认的回调方法,用于处理响应。该方法通过循环生成接下来四页的URL(从第1页到第4页)。对于每一页,它创建并产生一个新的Scrapy请求 (scrapy.Request
),并指定回调函数为self.parse_data
。
1 | import scrapy |
注意
在提供的parse
方法中,response
参数实际上没有被使用。这是因为该方法的主要目的是生成后续页面的URL,并对每个URL发起新的请求,而不是处理response
中的数据。
在常规的Scrapy爬虫中,parse
方法通常用于处理响应,从中提取数据或发现新的URL来跟进。但在这个特定的例子中,parse
方法仅用于根据初始响应生成一系列后续页面的请求。实际处理这些页面的响应内容的任务被委托给了另一个方法parse_data
(在后续定义)。这意味着在这个特定的parse
实现中,初始的response
对象不包含需要立即处理的相关数据,其主要作用是触发爬虫开始执行后续页面的请求。
显然请求的文件是JSON文件,因此需要编辑方法处理从网页请求中返回的JSON格式的响应。这个方法特别适用于处理返回JSON数据的API响应。
1 | import scrapy |
parse_data
是一个实例方法,与parse
方法类似,它是Scrapy框架用于处理响应的回调函数。不同的是,这个方法专门用于处理JSON响应。response
参数是一个包含HTTP响应数据的对象。在Scrapy中,这个对象通常包括响应的内容和其他元数据。dict_data = response.json()
这一行是核心功能。response.json()
方法将响应的内容(假定是JSON格式)解析成Python字典。这允许以Python原生的方式访问JSON响应中的数据。- 在将JSON响应解析为字典后,可以进一步处理这些数据。例如从字典中提取特定的字段,并将它们存储在Scrapy的Item对象中,或者直接进行数据清洗、转换等操作。
在修改settings.py
配置文件的参数为正确后进行调试,发现其response
依旧能返回200
,然而实际输入的URL和抓取时间戳timestamp
并不一样,这说明其对时间戳并不敏感。
如果敏感且失败则需要构造时间戳int(time.time() * 1000)
进行分析调试结果response.json()
返回的数据结构是一个字典且具有数据
但是由于这样数据有太多且杂乱无章,因此在获取数据的时候就进行部分的过滤,只获取Data
下的Posts
里的部分数据即可,例如名称,地址,时间,详情URL等
1 | import scrapy |
response.json()
将返回的JSON数据转换为Python字典。.get("Data").get("Posts")
从这个字典中获取Data
键对应的值,然后从Data
中获取Posts
键对应的值。JSON结构是{"Data": {"Posts": [...]}}
,这将得到包含职位信息的列表。- 遍历
dict_data
列表中的每个元素(每个元素代表一个职位)。 - 对于每个职位,创建一个新的字典
item
,并从data
中提取相关字段。 - 使用
PostId
来格式化self.PostURL
字符串,以创建指向每个职位详情页的URL。 item = {}
: 这行代码创建了一个空字典item
,用于存储从每个职位信息中提取的数据。这是一种常见的在爬虫中收集数据的方法。self.PostURL
是在访问当前类实例(在这种情况下是TencentDataSpider
的实例)的PostURL
属性。这意味着PostURL
是这个类的实例属性,而非一个局部变量或一个类变量(类变量将会使用类名来访问,比如TencentDataSpider.PostURL
)。- 从
data
字典中获取PostId
值,然后使用这个PostId
来格式化self.PostURL
字符串。 PostURL
为self.PostURL.format(PostId)
使用了format
方法来插入PostId
进行构造- 格式化后的URL被存储在
detail_url
变量中。然后,这个URL被用来生成一个新的请求,该请求被发送到parse_post_detail
方法(这是一个假定的方法,用于处理每个职位详情页面的响应)scrapy.Request
:- 这是Scrapy用来创建新HTTP请求的类。
detail_url
是这个新请求的目标URL。这个URL通常是从当前页面提取的,指向一个需要进一步爬取的页面。
callback=self.parse_post_detail
:callback
参数指定了Scrapy在收到此请求的响应后应该调用的方法。self.parse_post_detail
是定义的爬虫类中的一个方法。当Scrapy处理detail_url
的响应时,它会将响应数据传递给parse_post_detail
方法。
meta={'info': item}
:meta
是一个字典,用于在Scrapy的不同请求之间传递额外的数据。- 在这个例子中,
meta
字典包含一个键'info'
,其值为item
。这意味着可以在parse_post_detail
方法中通过response.meta['info']
来访问这个item
对象。 - 这种方式在请求链中保持状态或传递数据时非常有用。
yield
:- 使用
yield
关键字来生成请求对象。在Scrapy中,yield
的使用使得框架可以接管并异步处理这些请求。 - 这允许Scrapy根据需要进行请求的调度,而不是立即发出请求,从而更有效地管理网络资源和避免对目标网站造成过大压力。
- 使用
5. 处理详情页和Items 文件
随后定义parse_post_detail
方法和构造items.py
文件下的类属性
tencent_data.py
:
1 | def parse_post_detail(self, response): |
items.py
:
1 | # Define here the models for your scraped items |
然后调试检查查看传值是否成功
调试处理详情页相关方法前需要同时配置Items 文件,不然无法传值
6. 编辑Pipelines
在Settings.py
文件激活Pipelines后,配置Pipelines文件
1 | # Define your item pipelines here |
在Scrapy的管道(Pipeline)中,可以选择用不同的方式来处理文件的打开和关闭。
- 类定义:
class TxPipeline:
定义了一个名为TxPipeline
的新类。- 这个类是Scrapy的一个管道,它必须实现
process_item
方法。
process_item
方法:def process_item(self, item, spider):
是管道处理每个爬取的项目(item)的方法。- 每当爬虫提取出数据并生成
item
对象时,Scrapy框架会自动调用这个方法,并将item
对象和spider
对象作为参数传递给它。
- 文件写入操作:
with open('tencent.txt', 'a', encoding='utf-8') as f:
使用with
语句打开文件tencent.txt
。如果文件不存在,将会创建它。文件以追加模式('a'
)打开,意味着新写入的内容会被添加到文件的末尾,而不是覆盖原有内容。文件以utf-8
编码打开,这对于写入非ASCII字符(例如中文)很重要。f.write(str(item))
将item
对象转换为字符串,并写入文件。这里需要注意的是,str(item)
可能不会以最优雅的格式输出,特别是如果item
包含多个字段或嵌套结构时。在实际应用中,可能需要更精细的格式化方法,比如使用json.dumps
来生成更可读的JSON格式。
- 返回项目:
return item
:在处理完项目后,管道返回item
对象。这允许同一个item
被多个管道依次处理。在Scrapy中,项目(items)可以通过多个管道传递,每个管道都可以执行一些操作(如清洗数据、去重、写入数据库等)。
使用with
语句:
在TxPipeline
类中,process_item
方法使用了with
语句来打开文件:
1 | with open('tencent.txt', 'a', encoding='utf-8') as f: |
with
语句:Python中的with
语句用于包裹执行需要资源管理的代码块,比如文件操作。with
语句可以确保文件在使用完毕后被正确关闭,即使在处理文件过程中发生错误也是如此。- 优点:使用
with
语句的主要优点是它会自动处理文件的关闭。这种方式在每次process_item
被调用时打开文件,写入数据,然后立即关闭文件,非常适合写入少量数据。 - 缺点:如果
process_item
被频繁调用,这种方式可能会导致性能问题,因为每次调用都会打开和关闭文件。
使用open_spider
和close_spider
方法:
在之前的案例DbPipeline
类中,文件的打开和关闭是在open_spider
和close_spider
方法中处理的:
1 | def open_spider(self, spider): |
open_spider
和close_spider
方法:这两个方法分别在爬虫开始时和结束时被调用。在这里,文件在爬虫启动时被打开,并在爬虫关闭时被关闭。- 优点:这种方式对于处理大量数据更有效,因为它在整个爬虫过程中只打开和关闭文件一次。这减少了文件操作的开销,提高了性能。
- 缺点:如果爬虫在执行过程中出现异常并意外终止,可能导致文件没有正确关闭。这可能会导致数据丢失或文件损坏。
- 如果爬虫产生的数据量不大,或者更关心代码的简洁性,使用
with
语句是一个很好的选择。 - 如果爬虫产生大量数据,或者希望减少文件操作的开销,使用
open_spider
和close_spider
方法可能更合适。
检查
最后运行,产出文件tencent.txt
且具有相应的值则成功
数据库存储
首先在Ubuntu的MySQL
创建一个数据库叫tx
mysql> create database tx;
随后检查是否创建成功mysql> show databases;
:
然后切换数据库并检查内容:
use tx;
show tables;
检查无误后按照items.py
文件下的TxItem
类创建表格即可:
1 | # 爬虫存数据 必须要先有表格才能进行数据的增加 |
创建表格后通过show tables;
查看当前数据库的表格内容
使用desc txzp;
查看 txzp
表格内的内容属性
建立测试文件
tx_debug.py
:
1 | import pymysql |
- 这段脚本主要用于建立与MySQL数据库的连接,执行一些数据库操作(CRUD逻辑),然后关闭连接。
pymysql.connect()
函数用于创建与MySQL数据库的连接。conn.cursor()
方法创建一个游标对象,该对象可以用来执行SQL语句并获取结果。- 数据库的实际操作(如查询、插入数据等)需要在获取游标后进行。
- 执行修改数据库内容的操作(如INSERT, UPDATE, DELETE)后,需要调用
conn.commit()
来提交这些更改。 - 最后,使用
curs.close()
和conn.close()
关闭游标和连接,以释放资源。
注意
在运行测试文件前,如果使用虚拟机需要检查3306端口是否已经让虚拟机和主机映射,否则会产生 Mysql Python Connector No connection could be made because the target machine actively refused it
的报错。
当在虚拟机(例如使用VMware)中运行应用程序(如数据库服务器)并希望从主机(物理机器)访问时,需要进行端口映射。这是因为虚拟机通常运行在与主机隔离的网络环境中。端口映射确保从主机到虚拟机的网络通信能够正确进行。
解释端口映射
- 为什么需要端口映射:
- 虚拟机通常在私有网络中运行,这意味着它们对主机网络而言是不可见的。
- 端口映射使得主机可以通过指定的端口访问虚拟机中的服务。
- 示例解释:
- MySQL(端口3306): 假设在虚拟机中运行MySQL服务器,它默认监听3306端口。要从主机上的应用程序连接到这个MySQL服务器,需要将虚拟机的3306端口映射到主机的某个端口(也可以是3306)。
- SSH(端口22): 同理,如果想要通过SSH连接到虚拟机,需要将虚拟机的22端口映射到主机的某个端口(也可以是22)。
端口映射的步骤
- 打开VMware的虚拟网络编辑器。
- 选择用于虚拟机的网络适配器(通常是NAT模式)。
- 进入NAT设置,然后设置端口转发规则。
- 添加规则:指定主机端口,目标IP(虚拟机IP)和虚拟机端口。
如果是服务器而非虚拟机
- 物理服务器:
- 如果数据库运行在物理服务器上,通常不需要端口映射,因为服务器与本地机器或其他服务器都在同一网络或可通过互联网直接访问。
- 需要注意的是安全设置,如防火墙规则,确保只有授权的客户端能够访问特定端口。
- 云服务器:
- 在云平台(如AWS、Azure)上,可能需要配置安全组或网络安全规则来允许特定端口(如3306)的流量。
- 这类似于在物理服务器上设置防火墙规则。
在所有情况下,重要的是确保只有信任的客户端能够访问敏感服务(如数据库服务器),并且采用强密码和加密连接(如SSL)来保护数据安全。
配置步骤
1. 在 settings.py 中配置MySQL信息
在Scrapy项目的settings.py
文件中,配置数据库连接信息。这是一个字典,包含连接数据库所需的所有信息。
1 | # 配置mysql链接信息 |
2. 在 pipelines.py 中创建MySQL存储的类
在pipelines.py
文件中,创建一个Pipeline类,该类负责在爬虫开始时连接数据库,在处理每个item时存储数据,以及在爬虫结束时关闭数据库连接。注释掉之前的配置。
1 | import pymysql |
3. 注册Pipeline
在settings.py
文件中,将Pipeline添加到ITEM_PIPELINES
设置中。这里的数字表示优先级,范围是0到1000。注释掉之前的配置。
1 | ITEM_PIPELINES = { |
4. 编写连接MySQL的逻辑
在TxzpPipeline
类中,实现数据库连接和关闭逻辑。
1 | import pymysql |
- open_spider 方法:
- 在爬虫启动时被Scrapy框架自动调用。
- 从
spider
的设置 (settings.py
) 中获取名为DATABASE_CONFIG
的数据库配置信息。 - 如果配置类型是MySQL(
'type': 'mysql'
),则使用这些配置信息(如主机、端口、用户名、密码等)建立数据库连接。 - 创建一个用于执行数据库操作的游标。
- 方法定义:
open_spider(self, spider)
- 作用:
open_spider
是一个特殊的Scrapy方法,它在爬虫启动时自动调用。这是设置爬虫运行前所需资源的理想位置,例如建立数据库连接。 - 参数:
self
指向类的实例,而spider
是当前运行的爬虫实例。通过spider
,可以访问Scrapy项目的设置和其他与爬虫相关的属性。
- 作用:
- 获取数据库配置:
data_config = spider.settings.get("DATABASE_CONFIG")
- 作用:这一行从Scrapy的
settings.py
文件中获取数据库配置信息。将配置放在settings.py
中可以集中管理配置,并在需要时轻松更改,而无需修改代码本身。 - 为什么重要:这种做法增加了代码的灵活性和可维护性,因为可以在不同环境(如开发、测试、生产)中使用不同的数据库配置,而无需更改代码。
- 作用:这一行从Scrapy的
- 检查配置类型:
if data_config.get('type') == 'mysql':
- 作用:这个条件检查确保从
settings.py
中获取的配置是为MySQL数据库设计的。这是一个安全措施,以防配置被错误地设置。 - 为什么重要:在可能有多种数据库配置的情况下(例如MySQL、PostgreSQL等),这个检查确保Pipeline使用正确的配置。这有助于避免运行时错误和配置混淆。
- 建立MySQL连接:
self.conn = pymysql.connect(**data_config.get('config'))
- 作用:这行代码使用
pymysql
模块建立到MySQL数据库的连接。**data_config.get('config')
是一个字典解包操作,它将配置字典中的键值对作为参数传递给pymysql.connect
函数。 - 为什么重要:建立数据库连接是进行数据库操作的首要步骤。使用
pymysql
模块可以方便地与MySQL数据库交互。这种方法的优点是可以直接从配置文件中读取连接信息,提高了代码的可读性和可维护性。
- 作用:这行代码使用
- 创建数据库游标:
self.cursor = self.conn.cursor()
- 作用:创建一个数据库游标对象,该对象允许在数据库中执行SQL命令并处理结果。
- 为什么重要:游标是执行和管理数据库操作的关键。它不仅允许执行SQL语句,还能帮助有效地检索查询结果。在Scrapy中,通常会在
process_item
方法中使用此游标执行数据库操作。
- process_item 方法:
- 对爬虫爬取的每个item调用此方法。
- 这是数据处理和保存的主要地方。在这个方法中,可以编写将爬取的数据(
item
)保存到MySQL数据库的逻辑。 - 在这个示例中,该方法仅返回
item
,需要根据具体需求来实现数据插入的逻辑。
- close_spider 方法:
- 在爬虫结束时被Scrapy框架自动调用。
- 关闭数据库游标和连接,确保释放资源。
5.编写存储逻辑
实现process_item
方法以将每个item存储到MySQL数据库。
1 | def process_item(self, item, spider): |
方法定义
1 | def process_item(self, item, spider): |
process_item
:这是Scrapy框架中Pipeline对象的一个核心方法,用于处理从爬虫传递过来的每个item。self
:指向当前类(TxzpPipeline
)的实例。item
:这是一个从爬虫传递到Pipeline的数据项。它通常是一个类似字典的对象,包含了爬虫提取的数据。spider
:引用当前的爬虫实例,允许访问爬虫特定的功能或数据。
SQL 语句的定义
1 | sql = "INSERT INTO txzp(RecruitPostName, LocationName, LastUpdateTime, CategoryName, Requirement, Responsibility) VALUES(%s,%s,%s,%s,%s,%s)" |
- 这行代码定义了一个SQL插入语句,用于将数据插入到名为
txzp
的MySQL表中。 INSERT INTO txzp
:指定要插入数据的表名。(RecruitPostName, LocationName, LastUpdateTime, CategoryName, Requirement, Responsibility)
:这些是表中的列名,将为这些列插入数据。VALUES(%s,%s,%s,%s,%s,%s)
:这是参数化的查询部分,%s
是占位符,用于在执行时插入实际的数据值。
准备数据
1 | params = [ |
- 这部分代码创建了一个列表
params
,包含了要插入到数据库中的数据。 item.get('RecruitPostName')
等语句从item
对象中提取相应的数据。get
方法用于安全地访问字典键值,即使键不存在也不会引发错误。
执行SQL语句
1 | self.cursor.execute(sql, params) |
- 使用之前创建的数据库游标
self.cursor
来执行SQL语句。 execute
方法执行前面定义的SQL语句,并用params
列表中的值替换占位符%s
。- 这种参数化查询方法可以有效防止SQL注入攻击,比直接将字符串拼接到SQL语句更安全。
提交事务
1 | self.conn.commit() |
- 调用
self.conn.commit()
来提交事务,确保更改被保存到数据库。 - 在数据库操作中,
commit
是一个重要步骤,它使得执行的操作(如插入、更新、删除)成为永久性的。 - 如果不调用
commit
,那么即使执行了SQL语句,数据也不会被实际保存到数据库中。
执行确认
执行测试文件tx_debug.py
连接成功后执行测试文件db_debug.py
随后连接Linux检查数据库是否存储数据即可
Scray Request 类
Scrapy的Request
类是用于表示一个HTTP请求的基本类。以下是其构造函数的参数和一些特殊的meta
键值的详细说明:
构造函数参数
- url (
str
): 请求的URL。 - callback (
callable
): 当请求成功时,Scrapy将调用此回调函数处理响应。 - method (
str
): HTTP方法(如'GET'
,'POST'
等)。默认为'GET'
。 - headers (
dict
): 自定义的请求头。 - body (
str
或unicode
): 请求体内容。如果未提供,默认为空字符串。 - cookies (
dict
或[dict]
): 请求时附带的cookies。 - meta (
dict
): 包含此请求的额外信息,可以在不同的回调函数间共享。 - encoding (
str
): 编码类型,默认为'utf-8'
。用于URL编码和将body转换为bytes。 - priority (
int
): 请求的优先级。数字越大优先级越高,默认为0。 - dont_filter (
bool
): 如果设置为True
,则不会对请求进行去重过滤。 - errback (
callable
): 如果请求过程中发生错误,将调用此函数。 - flags (
list
): 一组字符串标志,通常用于日志记录或调试。 - cb_kwargs (
dict
): 传递给回调函数的关键字参数。
1 | from scrapy.http import Request,FormRequest |
Request.meta 的特殊键值
- dont_redirect (
bool
): 不处理HTTP重定向。 - dont_retry (
bool
): 不对失败的HTTP请求进行重试。 - handle_httpstatus_list (
list
): 包含HTTP状态码的列表,这些状态码的响应将不被视为错误。 - handle_httpstatus_all (
bool
): 处理所有HTTP状态码。 - dont_merge_cookies (
bool
): 不合并此请求的cookies。 - cookiejar (
int
/object
): 指定用于此请求的cookie jar。 - dont_cache (
bool
): 禁止对此请求使用HTTP缓存。 - redirect_reasons (
list
): 跟踪重定向的原因。 - redirect_urls (
list
): 存储请求过程中的所有重定向URL。 - bindaddress (
tuple
): 指定用于出站连接的本地IP地址和端口。 - dont_obey_robotstxt (
bool
): 不遵守robots.txt规则。 - download_timeout (
float
): 设置下载超时时间。 - download_maxsize (
int
): 设置允许下载的最大字节数。 - download_latency (
float
): 设置下载延迟。 - download_fail_on_dataloss (
bool
): 如果出现数据丢失,下载失败。 - proxy (
str
): 设置此请求的代理服务器。 - ftp_user (
str
): FTP请求的用户名。 - ftp_password (
str
): FTP请求的密码。 - referrer_policy (
str
): 设置引用策略。 - max_retry_times (
int
): 设置请求最大重试次数。
FormRequest 类
Scrapy框架中的FormRequest
类,它是Request
类的一个子类,专门用于处理表单请求。
FormRequest
是Scrapy用来发送数据到表单的特殊请求类型。它继承自基本的Request
类,并添加了处理表单数据的特定功能。
构造函数参数
\*args
和\**kwargs
:- 这些是Python中的标准参数,允许接收任意数量的位置参数(
*args
)和关键字参数(**kwargs
)。这在继承时使得FormRequest
可以接收Request
类接受的所有参数。
- 这些是Python中的标准参数,允许接收任意数量的位置参数(
formdata
(dict
或 类似于列表的元组对):- 这是用于传递表单数据的关键字参数。如果提供了
formdata
,FormRequest
将以application/x-www-form-urlencoded
格式编码这些数据并包含在请求体中。 - 如果
method
未指定且formdata
存在,则默认将请求方法设置为POST
。
- 这是用于传递表单数据的关键字参数。如果提供了
方法逻辑
- 检查
formdata
和method
:- 如果提供了
formdata
且未指定method
,则默认将method
设置为POST
。
- 如果提供了
- 调用基类构造函数:
- 使用
super(FormRequest, self).__init__(*args, **kwargs)
调用基类(Request
)的构造函数,确保所有基本的初始化逻辑被执行。
- 使用
- 处理
formdata
:- 如果提供了
formdata
,它将被转换为查询字符串。 - 对于
POST
请求,这个查询字符串将被设置为请求体,同时设置Content-Type
头为application/x-www-form-urlencoded
。 - 对于非
POST
请求(例如GET
),查询字符串将被附加到URL上。
- 如果提供了
特别说明
valid_form_methods
是一个类属性,定义了FormRequest
支持的HTTP方法。默认为['GET', 'POST']
。_urlencode
函数用于将表单数据转换为URL编码的字符串。_set_body
和_set_url
是内部方法,用于设置请求体和更新请求URL。
FormRequest
通常用于向网站提交表单,例如登录页面、搜索查询等。由于它自动处理表单数据的编码,因此相比于普通的Request
,在处理表单请求时更加方便。
在使用scrapy发动POST请求的时候,常使用此方法,能较方便的发送请求.具体的使用,见登录github案例;
1 | class FormRequest(Request): |
HTTP响应处理
当使用Scrapy或类似的网络爬虫框架时,处理HTTP响应是一个常见的任务。以下是响应对象的主要属性和方法:
- url(字符串): 此响应的URL。
- status(整数): 响应的HTTP状态码。默认为200。
- headers(字典): 此响应的响应头。可以是单值或多值。
- body(字节): 响应主体。为字节类型,可使用
response.text
以字符串形式访问。 - flags(列表): 包含初始响应标志的列表。
- request(Request对象): 表示生成此响应的请求。
属性和方法
- url: 包含请求URL的字符串。只读。
- method: 表示HTTP方法的字符串。
- headers: 请求头的类似字典对象。
- body: 包含请求正文的字符串。只读。
- meta: 包含请求的任意元数据的字典。
- copy(): 返回此请求的副本。
- replace(): 返回一个具有更新字段的新请求。
Scrapy日志配置和使用
在Scrapy项目中,正确配置和使用日志是监控爬虫行为、调试和记录关键信息的重要方面。
日志文件配置
在Scrapy的settings.py
文件中,可以配置以下日志相关的设置:
- LOG_FILE: 设置日志输出文件的路径。如果设置为
None
,日志将输出到控制台。 - LOG_ENABLED: 控制是否启用日志记录,布尔值,默认为
True
。 - LOG_ENCODING: 设置日志文件的编码,默认为
'utf-8'
。 - LOG_LEVEL: 设置日志级别,默认为
'DEBUG'
。其他级别包括'INFO'
、'WARNING'
、'ERROR'
、'CRITICAL'
。 - LOG_FORMAT: 自定义日志的格式。格式选项包括:
%(levelno)s
: 日志级别的数值。%(levelname)s
: 日志级别名称。%(pathname)s
: 执行程序的路径。%(filename)s
: 执行程序名。%(funcName)s
: 日志的当前函数。%(lineno)d
: 日志的当前行号。%(asctime)s
: 日志记录的时间。%(thread)d
: 线程ID。%(threadName)s
: 线程名称。%(process)d
: 进程ID。%(message)s
: 日志信息。%(name)s
: 日志记录器的名称。
- LOG_DATEFORMAT: 设置日志的日期格式。
- LOG_STDOUT: 布尔值,控制是否将标准输出(stdout)重定向到日志,通常设为
False
。 - LOG_SHORT_NAMES: 布尔值,设置为
True
时,日志记录器将使用短名称。
Python日志模块
1 | import logging |
- 在这个示例中,创建了一个名为
hello
的日志记录器。 - 两个处理器(
StreamHandler
和FileHandler
)分别用于输出日志到控制台和文件。 - 使用
Formatter
设置了日志的格式。 - 日志记录器通过添加这些处理器来启用日志记录。
项目中的常见设置
1 | LOG_FILE = 'logfile_name.log' |
- logger: Scrapy在每个Spider实例中提供了一个可用的logger实例,用于记录日志。
注意事项
- 在生产环境中,通常将日志级别设置为
INFO
或WARNING
,以减少日志文件的大小。 - 保证日志记录的详细程度与应用程序的需求相符,同时避免记录过于敏感的信息,如用户凭据等。
- 日志文件的管理(如归档和清理)也是重要的,以避免日志文件占用过多磁盘空间。
GitHub登录过程分析与实现
登录分析
首先前往GitHub https://github.com/login 进行登录操作,检查登录的过程提交的数据
可以发现其在登录的过程中进行了POST
请求,并提交表单到 https://github.com/session
。关键在于准确地捕获并发送所有必要的表单参数。
检查其Payload
可以查看到其参数和对应的值
由于密码和账户组合有三种情况,因此通过分别保持密码和账户相同分别再进行两次请求,并查看它们的值
比较三者的不同可以发现其是authenticity_token
,login
,password
,required_field_####
,timestamp
和timestamp_secret
,因此需要在后面进行构造
登录参数
因此登录GitHub时,提交的表单数据不仅包含用户名和密码,还包括一些隐藏字段,如authenticity_token
、timestamp
和timestamp_secret
。这些字段可能是为了安全性(如防止CSRF攻击)而设置的。
一个典型的登录请求包含以下参数:
1 | form_data = { |
form_data 数据来源
这些数据大部分可以在访问 https://github.com/login
页面时从页面HTML中捕获。特别是authenticity_token
、required_field_
、timestamp
和timestamp_secret
是动态生成的,因此每次登录前都需要先访问登录页面来获取这些数据。
请求流程
请求流程
- 访问
https://github.com/login
以获取登录所需的参数。 - 向
https://github.com/session
提交POST请求,携带用户名、密码及其他必需数据。
Scrapy实现
编辑爬虫文件
1 | import scrapy |
代码解析
1 | class GithubLoginSpider(scrapy.Spider): |
- 定义了一个名为
GithubLoginSpider
的Scrapy爬虫类。 name
属性设置为"github_login"
,是爬虫的唯一标识。allowed_domains
列表限制了爬虫只能爬取"github.com"
域名下的页面。start_urls
包含了爬虫开始爬取的URL,这里是GitHub的登录页面。
1 | def parse(self, response): |
parse
是Scrapy爬虫的默认回调方法,爬虫向一个URL发出请求时,获得的响应会自动传递给这个方法。在这个例子中,response
对象包含了对start_urls
中URL的HTTP响应。- 这几行代码使用XPath从登录页面的HTML中提取了
authenticity_token
、required_field
、timestamp
和timestamp_secret
。这些字段通常用于防止跨站请求伪造(CSRF)攻击。response.xpath('//input[@name="authenticity_token"]/@value').get()
//input[@name="authenticity_token"]
:这部分的XPath查找文档中所有<input>
元素,其中name
属性等于"authenticity_token"
。//
表示在整个文档中查找,而不仅限于某个特定部分。/@value
:这部分表示从找到的<input>
元素中提取value
属性。在HTML中,<input>
标签的value
属性通常用来存储输入字段的值。.get()
:这是Scrapy的Selector对象的方法,用于提取XPath选择器的第一个匹配结果。如果没有匹配的元素,它将返回None
。
response.xpath('//input[@type="text" and @hidden="hidden"]/@name').get()
//input[@type="text" and @hidden="hidden"]
:这个XPath查找所有<input>
元素,它们的type
属性为"text"
且同时拥有hidden="hidden"
属性。这通常是隐藏的表单字段,对用户不可见,但对于表单提交可能是必需的。/@name
:这表示提取这些<input>
元素的name
属性。.get()
:同样,这是用来获取第一个匹配结果的Scrapy方法。
response.xpath('//input[@name="timestamp"]/@value').get()
//input[@name="timestamp"]
:这个XPath寻找所有<input>
元素,其name
属性为"timestamp"
。这通常用于跟踪表单的创建或提交时间。/@value
:提取这些<input>
元素的value
属性。.get()
:获取第一个匹配结果。
response.xpath('//input[@name="timestamp_secret"]/@value').get()
//input[@name="timestamp_secret"]
:查找所有<input>
元素,其name
属性为"timestamp_secret"
。这个值可能是与timestamp
相关的加密或哈希值。/@value
:提取value
属性。.get()
:获取第一个匹配结果。
1 | form_data = { |
- commit: 表单提交按钮的值,通常在登录表单中可以找到。
- authenticity_token: 一个安全令牌,用于防止CSRF攻击,从登录页面的HTML中提取。
- login: GitHub的用户名,
USER.LOGIN
应该替换为实际的用户名。 - password: GitHub的密码,
USER.PASSWORD
应该替换为实际的密码。 - webauthn-conditional, javascript-support, webauthn-support, webauthn-iuvpaa-support: 这些字段可能与GitHub的特定前端逻辑相关,例如Web认证和JavaScript支持。
- return_to: 登录后应重定向到的URL。
- allow_signup, client_id, integration: 这些可能是额外的表单字段,用于GitHub的内部跟踪或逻辑。
- required_field: 之前从页面提取的隐藏字段,其确切意图可能是内部验证。
- timestamp 和 timestamp_secret: 与表单提交时效性和安全性相关的字段。
1 | yield scrapy.FormRequest(url="https://github.com/session", formdata=form_data, callback=self.login) |
- 这行代码创建了一个Scrapy的
FormRequest
对象,用于向GitHub的https://github.com/session
URL发送一个POST请求。 formdata=form_data
指定了要发送的表单数据。callback=self.login
指定了Scrapy在收到响应后应调用的方法。self.login
方法将处理登录请求的响应。
1 | def login(self, response): |
def login(self, response):
- 这行定义了一个名为login
的方法。由于存在self
参数,可以判断这个方法属于某个类,并且这个方法接受一个参数response
。if 'CosmicTrace' in response.text:
- 这行代码检查response.text
中是否包含字符串 ‘CosmicTrace’。如果包含,意味着满足了某种成功的条件。print("已成功拿到值")
- 如果在response.text
中找到了字符串,就打印出 “已成功拿到值”,表示操作成功。else:
- 如果在response.text
中没有找到字符串 ‘CosmicTrace’,则执行这部分代码。print('失敗了')
- 如果没有找到字符串,就打印出 ‘失敗了’,即“失败了”。print(response)
- 这行代码会在执行完if-else条件后打印整个response
对象。这对于调试或记录日志很有用,可以查看请求的完整响应。
测试文件
编辑调试文件
1 | from scrapy.cmdline import execute |
Settings 文件
编辑配置文件
1 | # Scrapy settings for git project |
测试执行
调试测试文件,断点在login
方法,发现其返回200,输出响应的TXT
格式,复制其内容保存为HTML
文件并打开
可以发现其成功显示出登录后的界面
也可以通过判断可以发现HTML
是否含有账号名来判断调试是否成功
Scrapy下载中间件(Downloader Middleware)
下载中间件是Scrapy的核心组件之一,它提供了一个灵活的方式来自定义请求和响应的处理过程。
基本概念
- 作用:下载中间件用于拦截并处理Scrapy发出的所有HTTP请求和响应。它们在Scrapy的请求/响应处理过程中提供了多个钩子(hooks)点。
- 功能:可以用于修改请求和响应、处理重定向、重试失败的请求、设置代理、处理cookies等。
- 实现:它是通过实现特定的方法的Python类来定义的。
内置中间件
Scrapy自带了多种下载中间件,这些中间件提供了对不同方面的处理支持。
可以通过运行命令
scrapy settings --get=DOWNLOADER_MIDDLEWARES_BASE
查看Scrapy自带的所有下载中间件及其优先级。1
{"scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware": 100, "scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware": 300, "scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware": 350, "scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware": 400, "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": 500, "scrapy.downloadermiddlewares.retry.RetryMiddleware": 550, "scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware": 560, "scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware": 580, "scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware": 590, "scrapy.downloadermiddlewares.redirect.RedirectMiddleware": 600, "scrapy.downloadermiddlewares.cookies.CookiesMiddleware": 700, "scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": 750, "scrapy.downloadermiddlewares.stats.DownloaderStats": 850, "scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware": 900}
这些内置中间件包括对robots.txt的处理、HTTP代理支持、Cookies处理等。
用户可以通过在
DOWNLOADER_MIDDLEWARES
设置中添加自定义的中间件来扩展Scrapy的功能。设置是一个字典,键是中间件类的路径,值是中间件的顺序(0-1000之间的整数)。数字越小,优先级越高,即越接近Scrapy引擎
1
2
3DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomMiddleware': 543,
}
详见官方文档
https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
下载中间件实例
- RobotsTxtMiddleware
- 功能描述: 此中间件用于处理网站的robots.txt规则。它会自动解析并遵守目标网站的robots.txt文件,从而限制爬虫的行为以符合网站的爬取政策。
- 配置方法: 默认情况下,Scrapy会遵守robots.txt规则。可以通过在项目的
settings.py
中设置ROBOTSTXT_OBEY = False
来禁用此功能。
- HttpAuthMiddleware
- 功能描述: 管理HTTP认证的中间件,用于处理那些需要HTTP认证(如基本认证、摘要认证)的网站。
- 配置方法: 可以通过在Scrapy的
Request
对象中指定http_user
和http_pass
属性来启用HTTP认证。
- DownloadTimeoutMiddleware
- 功能描述: 设置下载请求的超时时间。如果请求在指定时间内未得到响应,它将被视为失败,并且可以被重试中间件重新处理。
- 配置方法: 可以通过设置
DOWNLOAD_TIMEOUT
来调整全局超时时间。
- DefaultHeadersMiddleware
- 功能描述: 为所有的Scrapy请求设置默认HTTP头部。这对于为每个请求添加或覆盖特定的HTTP头(如Accept-Language)非常有用。
- 配置方法: 在
settings.py
文件中,使用DEFAULT_REQUEST_HEADERS
设置来定义默认的HTTP头部。
- UserAgentMiddleware
- 功能描述: 此中间件允许为每个请求随机或固定地设置User-Agent。User-Agent通常被网站用来识别访问者使用的浏览器和操作系统。
- 配置方法: 可以通过在
settings.py
中设置USER_AGENT
或者使用自定义的User-Agent提供器来改变User-Agent。
- RetryMiddleware
- 功能描述: 处理失败的HTTP请求并尝试重新发送。这对于处理暂时的网络问题或服务器错误非常有用。
- 配置方法: 默认情况下,Scrapy会重试失败的请求。可以在
settings.py
中修改RETRY_TIMES
来调整重试次数。
- AjaxCrawlMiddleware
- 功能描述: 用于处理JavaScript生成的页面。这使得Scrapy能够爬取那些需要执行JavaScript代码才能显示完整内容的页面。
- 配置方法: 默认不启用,需要在
settings.py
中显式启用。
- MetaRefreshMiddleware
- 功能描述: 自动处理页面的meta刷新标签。有些网页可能会使用meta标签来自动刷新或重定向到另一个页面。
- 配置方法: 默认启用。可以通过在
settings.py
中设置METAREFRESH_ENABLED = False
来禁用它。
- HttpCompressionMiddleware
- 功能描述: 此中间件自动处理压缩的HTTP响应,例如gzip或deflate格式的内容。
- 配置方法: 默认启用,无需特别配置。
- RedirectMiddleware
- 功能描述: 自动处理HTTP重定向。对于301和302类型的重定向,此中间件会自动跟随重定向链接。
- 配置方法: 默认启用,可以通过
REDIRECT_ENABLED
设置来禁用。
- CookiesMiddleware
- 功能描述: 管理Cookies。对于需要管理多个会话或跟踪用户会话的爬虫来说,这个中间件非常有用。
- 配置方法: 默认启用,可以通过
COOKIES_ENABLED = False
来禁用。
- HttpProxyMiddleware
- 功能描述: 处理HTTP代理。通过使用代理,爬虫可以从不同的IP地址发送请求,这有助于绕过IP封锁或进行匿名抓取。
- 配置方法: 可以通过在请求的
meta
中设置proxy
键来启用代理。
- DownloaderStats
- 功能描述: 收集下载统计信息。这个中间件为每个响应或异常收集统计数据,帮助分析和优化爬虫性能。
- 配置方法: 默认启用,一般无需更改配置。
- HttpCacheMiddleware
- 功能描述: 提供对HTTP缓存的支持。此中间件能够缓存请求的响应,以便下次请求相同资源时快速获取。
- 配置方法: 默认不启用,可以通过在
settings.py
中设置HTTPCACHE_ENABLED = True
来启用。
下载中间件API
- 每个下载中间件可以实现以下一个或多个方法:
process_request(request, spider)
: 在发送请求之前调用。可以返回None、一个Response对象、一个Request对象或抛出一个异常。process_response(request, response, spider)
: 在接收到响应后调用。可以返回Response对象或抛出异常。process_exception(request, exception, spider)
: 当下载处理过程中发生异常时调用。from_crawler(cls, crawler)
: 类方法,用于访问Scrapy的核心组件和API,以及创建中间件实例。
返回值的重要性
- 每个方法的返回值都非常重要,它决定了请求或响应接下来的处理流程。
- 如果
process_request
返回非None值,Scrapy将不会继续处理该请求,而是立即调用相应的process_response
方法。 - 如果
process_response
返回一个新的请求(Request对象),Scrapy将停止调用其他中间件的process_response
方法,转而处理这个新的请求。 - 在
process_exception
中返回一个新的请求对象将同样导致Scrapy停止调用其他中间件的process_exception
方法,并处理这个新的请求。
- 如果
自定义中间件
UA代理池中间件
在Scrapy项目中,使用自定义中间件来实现用户代理池(User-Agent Pool)是一种常见的做法,用于避免被目标网站识别并可能被阻止。
自定义的用户代理池中间件允许每个请求随机使用不同的用户代理(User-Agent),从而减少被目标网站识别为爬虫的风险。
1. 在 settings.py 中定义用户代理列表
首先,在Scrapy项目的settings.py
文件中定义一个用户代理列表:
settings
文件 user_agent_list
1 | #user_agent |
这个列表包含了多个不同的用户代理字符串,用于在发起请求时模拟不同的浏览器。
2. 在 middlewares.py 中实现用户代理中间件
在middlewares.py
文件中实现自定义的中间件,用于在每个请求中随机选择一个用户代理:
1 | import random |
RandomUserAgentMiddleware
类定义了一个process_request
方法,该方法在每个请求发送之前被调用。- 方法中,随机从
USER_AGENT_LIST
中选取一个用户代理,并将其设置为该请求的User-Agent
。
信息
也有另一种方法是直接从 settings
模块导入
1 | from .settings import USER_AGENT_LIST |
- 这种方法直接从
settings.py
文件中导入user_agent_list
。 - 优点是直接和简洁,尤其是在用户代理列表只在这个中间件中使用时。
- 缺点是它降低了配置的灵活性。如果想在不同的环境(例如开发环境和生产环境)中使用不同的用户代理列表,或者希望能够通过命令行参数动态覆盖这些设置,这种方法可能不太适用。
- 之前的方法使用Scrapy的内置
settings
模块来访问项目设置(在settings.py
文件中定义的设置)。 - 优点是它利用了Scrapy框架的设置管理机制,可以更容易地在整个项目中管理和维护这些设置。
- 缺点是需要导入
scrapy.conf.settings
,这在某些情况下可能稍显冗余。 - 如果倾向于使用Scrapy框架的标准特性,并希望在项目的不同组件间共享配置,那么第一种方法(使用
scrapy.conf.settings
)可能更适合。 - 如果项目结构比较简单,或者只在一个地方使用这个用户代理列表,那么第二种方法(直接从
settings.py
导入)可能更直接有效。
3. 在 settings.py 中启用中间件
在settings.py
文件中,将自定义的中间件添加到DOWNLOADER_MIDDLEWARES
设置中,以启用该中间件:
1 | DOWNLOADER_MIDDLEWARES = { |
- 替换
'myproject.middlewares.RandomUserAgentMiddleware'
为实际的中间件路径。 - 数值
400
是中间件的优先级。可以根据需要调整这个值以控制中间件的执行顺序。
注意事项
- 使用用户代理池可以帮助模拟常规用户的浏览行为,但应注意合理使用以避免对目标网站造成不必要的负担。
- 请确保遵守目标网站的爬虫政策和使用条款。
- 用户代理字符串应尽量选择常见且更新的版本,以提高爬虫的隐蔽性。
IP代理池
在Scrapy项目中使用IP代理池是为了隐藏爬虫的真实IP地址,这有助于绕过目标网站的IP封锁或请求频率限制。
实现
IP代理池可以让Scrapy爬虫在每次请求时使用不同的IP地址,从而提高爬虫的匿名性和效率。
1. 在 settings.py 中定义IP代理池
在Scrapy项目的settings.py
文件中,定义一个包含多个代理IP的列表:
(此处是示例,以下的代理基本无法使用的,同时不建议去找免费的代理,不安全)
1 | # IP代理池 |
这个列表包含了多个字典,每个字典代表一个代理服务器,其中ipaddr
键的值是代理服务器的IP地址和端口。
2. 在 middlewares.py 中实现IP代理中间件
在middlewares.py
文件中,实现一个中间件来随机使用IP代理池中的一个代理:
1 | import random |
ProxyMiddleware
类定义了一个process_request
方法,该方法在每个请求发送之前被调用。- 方法中,随机从
IPPOOL
中选取一个IP代理,并将其设置为该请求的代理。
信息
第一种方法是从预定义的IP代理池中随机选择一个代理,而另一种种方法是实时从一个API获取代理IP
1 | import requests |
选择哪种方法取决于具体需求和环境。如果需要更高的灵活性和更大的代理IP池,且不介意依赖外部服务和可能的额外成本,那么使用实时API获取代理可能是更好的选择。如果更注重稳定性和控制,且不希望增加额外的依赖和成本,那么使用预定义的代理池可能更适合。
3. 在 settings.py 中启用自定义中间件
在settings.py
文件中,将自定义的中间件添加到DOWNLOADER_MIDDLEWARES
设置中,以启用该中间件:
1 | DOWNLOADER_MIDDLEWARES = { |
- 替换
'myproject.middlewares.ProxyMiddleware'
为实际的中间件路径。 - 数值
543
是中间件的优先级。可以根据需要调整这个值以控制中间件的执行顺序。
注意事项
- 请注意,示例中的代理IP可能无法使用。在实际应用中,建议使用可靠且安全的付费代理服务。
- 免费代理可能不稳定,且有安全隐患,例如可能会被用于拦截或篡改数据。
- 使用代理时,请确保遵守目标网站的爬虫政策和使用条款,合理使用以避免对目标网站造成不必要的负担。
Scrapy settings.py 配置详解
优先级
settings.py
文件是 Scrapy 项目的核心配置文件,用于定义爬虫的行为和项目的全局设置。
基础配置
项目名称:
BOT_NAME
: 定义项目名称,通常用作日志记录的标识。1
BOT_NAME = 'baidu'
爬虫模块路径:
SPIDER_MODULES
: 指定包含 Scrapy 爬虫的模块。NEWSPIDER_MODULE
: 定义使用genspider
命令创建新爬虫时的默认模块。1
2SPIDER_MODULES = ['baidu.spiders']
NEWSPIDER_MODULE = 'baidu.spiders'
用户代理 (User-Agent):
USER_AGENT
: 定义爬虫默认使用的用户代理。1
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
Robots.txt 协议:
ROBOTSTXT_OBEY
: 设置是否遵守网站的 robots.txt 协议。1
ROBOTSTXT_OBEY = False
Cookies 支持:
COOKIES_ENABLED
: 设置是否启用 cookies。1
COOKIES_ENABLED = True
Telnet 控制台:
TELNETCONSOLE_ENABLED
: 设置是否启用 Telnet 控制台用于查看爬虫运行情况。1
TELNETCONSOLE_ENABLED = True
默认请求头:
DEFAULT_REQUEST_HEADERS
: 设置 Scrapy 发送 HTTP 请求时默认使用的请求头。1
2
3
4DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
请求重试:
RETRY_ENABLED
: 设置是否启用请求重试。RETRY_TIMES
: 设置请求重试的次数。RETRY_HTTP_CODECS
: 设置触发重试的 HTTP 状态码。1
2
3RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODECS = [500, 502, 503, 504, 408]
并发与延迟
最大并发请求数:
CONCURRENT_REQUESTS
: 设置下载器最大处理的并发请求数量。1
CONCURRENT_REQUESTS = 32
每个域的最大并发请求数:
CONCURRENT_REQUESTS_PER_DOMAIN
: 设置每个域名的最大并发请求数。1
CONCURRENT_REQUESTS_PER_DOMAIN = 16
每个IP的最大并发请求数:
CONCURRENT_REQUESTS_PER_IP
: 设置每个 IP 的最大并发请求数。如果设置,CONCURRENT_REQUESTS_PER_DOMAIN
将被忽略。1
CONCURRENT_REQUESTS_PER_IP = 16
下载延迟:
DOWNLOAD_DELAY
: 设置对同一网站的请求间隔秒数。1
DOWNLOAD_DELAY = 3
智能限速 (AutoThrottle)
AutoThrottle 扩展:
- 自动调整 Scrapy 到最佳爬取速度,减轻对目标站点的压力。
AUTOTHROTTLE_ENABLED
: 开启 AutoThrottle。AUTOTHROTTLE_START_DELAY
: 初始下载延迟。AUTOTHROTTLE_MAX_DELAY
: 最大下载延迟。AUTOTHROTTLE_TARGET_CONCURRENCY
: 每秒并发请求数的目标值。AUTOTHROTTLE_DEBUG
: 开启调试模式,以观察 AutoThrottle 的行为。
1 | AUTOTHROTTLE_ENABLED = True |
中间件、Pipelines、扩展
启用或禁用 Spider 中间件:
1
2
3SPIDER_MIDDLEWARES = {
'baidu.middlewares.BaiduSpiderMiddleware': 543,
}启用或禁用下载器中间件:
1
2
3DOWNLOADER_MIDDLEWARES = {
'baidu.middlewares.MyCustomDownloaderMiddleware': 543,
}启用或禁用扩展:
1
2
3EXTENSIONS = {
'scrapy.extensions.telnet.TelnetConsole': None,
}配置 Item Pipelines:
- ITEM_PIPELINES: 设置启用的 Item Pipeline。
1
2
3ITEM_PIPELINES = {
'baidu.pipelines.CustomPipeline': 300,
}
Spider类下载图片
在Scrapy框架中,下载图片与下载文本数据确实有一些相似之处,但也有其特有的处理方式。Scrapy通过内置的支持使得下载图片变得相对简单。与处理文本数据不同,处理图片通常涉及处理二进制数据,并可能需要额外的中间件支持。
测试文件
1 | from scrapy.cmdline import execute |
手动保存图片
Spider文件
1 | import scrapy |
代码解释
- 定义一个继承自
scrapy.Spider
的爬虫类BaiduImgSpider
。 name
:为爬虫指定一个唯一的名称。allowed_domains
:定义爬虫允许爬取的域名列表。这里为空,表示不对域名进行限制。start_urls
:包含一个起始URL的列表,该URL是百度图片搜索的结果页面。parse
:是爬虫的一个方法,处理响应并提取数据。- 使用正则表达式从页面源代码中提取所有图片的URL。正则表达式
'thumbURL":"(.*?)"'
用于匹配图片的URL。 - 遍历所有提取到的图片URL。
- 对每个图片URL,生成一个Scrapy请求,并将响应发送到
get_img
方法。 get_img
:处理图片下载的方法。response.body
:获取响应的二进制数据,即图片内容。- 检查是否存在名为
imgspider
的目录,如果不存在,则创建该目录。用于保存下载的图片。 - 构造图片文件的保存路径和文件名。这里使用
self.num
来为图片生成唯一的文件名,但需要注意self.num
在爬虫类中初始化。 - 以二进制写入模式打开文件,将图片数据写入文件。
注意事项
- 该爬虫直接从百度图片的搜索结果页面提取图片URL,具体的URL模式可能会随着百度网站的更新而变化。
- 使用正则表达式提取数据可能不如使用XPath或CSS选择器那样稳定,因为如果百度网页的结构发生变化,正则表达式可能需要更新。
- 确保遵守百度图片的版权和使用条款,不要用于任何侵犯版权或违法的用途。
调试处理前配置Settings文件进行伪装,否则连接失败
Pipeline 保存图片
Items 文件
1 | # Define here the models for your scraped items |
- 定义一个名为
BaiduItem
的Item类,该类用于在爬虫和Pipeline之间传递数据。 - 它继承自
scrapy.Item
。在Scrapy中,Item
是用来收集从网页提取的数据的简单容器。 BaiduItem
类将被用来存储从百度图片搜索结果中提取的数据。BaiduItem
类中定义了一个字段img_data
。scrapy.Field()
是Scrapy用来定义Item字段的特殊容器,用于存储从网页中提取的数据。- 在这个例子中,
img_data
字段用于存储图片的二进制数据或图片的URL。 - 创建了一个
BaiduItem
的实例,并将提取到的图片数据或URL填充到img_data
字段。 - 然后,可以将这个填充了数据的
BaiduItem
实例传递给Pipeline进行进一步的处理,例如保存图片到文件系统或数据库
Spider 文件
1 | import scrapy |
- 定义一个名为
BaiduImgSpider
的爬虫类,指定爬虫名称、允许的域名(在这里为空)和起始URL。 parse
是Scrapy爬虫处理响应的默认方法。使用正则表达式从响应中提取图片的URL。- 对每个找到的图片URL,发起一个新的Scrapy请求,并指定回调方法
get_img
来处理这些请求。 get_img
方法处理图片下载的响应。创建一个BaiduItem
实例,并将下载的图片数据(二进制格式)存储在img_data
字段中。- 将包含图片数据的item提交给Pipeline进行进一步处理。
Pipelines 文件
1 | from itemadapter import ItemAdapter |
- 导入了所需的模块和类。
ItemAdapter
用于适配不同类型的Item对象,os
模块用于处理文件和路径操作。 num
是一个类变量,用于生成图片文件的名称。process_item
是Pipeline处理item的方法。- 检查名为
imgpipeline
的文件夹是否存在,如果不存在,则创建它。这个文件夹用于存储下载的图片。 - 为下载的图片生成文件名,并将
num
递增以确保文件名的唯一性。 - 以二进制写模式打开文件,并将图片数据写入文件。
- 返回处理后的item。
settings.py
中激活BaiduPipeline
避免没有传值
注意事项
- 这个爬虫和Pipeline的实现假定图片的URL可以直接从百度图片搜索结果的页面HTML中提取。如果百度更改其HTML结构或JavaScript动态加载机制,这个方法可能需要更新。
- 当处理大量图片或大型网站时,请确保遵循robots.txt规则并尊重网站的版权和使用条款。
ImagesPipeline类下载图片
Scrapy提供了一个专门的ImagesPipeline
类,用于方便地下载和处理图片。要正确使用这个类,需要按照以下步骤操作:
在Spider文件中提取图片URLs
- 爬虫应该解析目标页面,提取图片的URLs,并将它们存储在item的一个字段中。
在Items文件中定义
image_urls
字段- 定义一个Scrapy Item,并包含一个名为
image_urls
的字段,用于存储待下载的图片URLs。
- 定义一个Scrapy Item,并包含一个名为
1 | class BdImagePipeItem(scrapy.Item): |
- 创建继承自ImagesPipeline的管道类
- 创建一个新的Pipeline类,继承自
ImagesPipeline
。这个类可以被用来进一步自定义图片的下载和处理行为(如过滤、转换格式等)。
- 创建一个新的Pipeline类,继承自
1 | from scrapy.pipelines.images import ImagesPipeline |
- 在Settings文件中配置图片存储路径和Pipeline
- 在项目的
settings.py
文件中,设置图片存储路径(IMAGES_STORE
)和启用图片管道。
- 在项目的
1 | IMAGES_STORE = '/path/to/your/images/dir' |
安装Pillow库
ImagesPipeline
需要Pillow库来处理图片。确保安装了Pillow库(版本4.0或以上)。
1 | pip install Pillow |
媒体管道的特性和设置
Scrapy的媒体管道(包括
ImagesPipeline
)提供了一些有用的特性,例如避免重新下载最近下载的媒体、生成缩略图、检查图像尺寸等。除了
IMAGES_STORE
之外,还有许多其他设置可以用来定制媒体管道的行为,如IMAGES_EXPIRES
、IMAGES_THUMBS
、IMAGES_MIN_HEIGHT
、IMAGES_MIN_WIDTH
等。
媒体管道的特性
Scrapy的媒体管道提供了强大的功能来处理下载的媒体文件,如图片和文件。特别是对于图像,Scrapy提供了额外的处理功能。
基本特性
- 避免重复下载:媒体管道会跟踪最近下载的文件,避免重复下载相同的媒体内容。
- 灵活的存储位置:支持多种存储方式,包括本地文件系统、Amazon S3、谷歌云存储等。
图像特有功能
- 格式转换:下载的图片会被转换为通用的JPG格式,并确保图片模式为RGB。
- 缩略图生成:可以自动生成指定尺寸的缩略图。
- 尺寸过滤:通过设置,可以过滤掉低于指定宽度或高度的图片。
媒体管道的设置
为了使用媒体管道,需要在项目的settings.py
文件中进行相应的配置。
启用媒体管道
1 | ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1} |
- 这行代码启用了Scrapy的图像管道,并设置其优先级为1。
设置存储位置和字段
1 | FILES_STORE='/path/to/valid/dir' 文件管道存放位置 |
Spider文件
1 | import scrapy |
- 导入了Scrapy框架、正则表达式库和项目中定义的
BaiduItem
。 - 定义了一个名为
BaiduImgSpider
的爬虫类。 start_urls
包含了开始爬取的URL(百度图片搜索结果)。parse
方法处理响应并提取图片URL。- 使用正则表达式从页面中提取图片的URL。
- 创建一个
BaiduItem
实例,并将提取到的URL列表赋值给image_urls
字段。 - 使用
yield
语句返回这个item。
Items文件
1 | # Define here the models for your scraped items |
- 定义了一个名为
BaiduItem
的Item类,包含一个image_urls
字段。 image_urls
用于存储待下载的图片URL列表。
Pipelines文件
1 | # Define your item pipelines here |
- 导入了Scrapy的
ImagesPipeline
。 - 定义了一个名为
BaiduPipeline
的类,它继承自ImagesPipeline
。 - 目前这个类没有进行任何自定义操作,直接继承了
ImagesPipeline
的全部功能。
Settings文件
添加图片管道存放位置IMAGES_STORE='imgStore'
添加缩略图尺寸设置:
1 | IMAGES_THUMBS= { |
ImagesPipeline类方法重写 (改名与翻页)
Spider文件
1 | import scrapy |
page_url
定义了用于翻页的URL模板。page_num
用于追踪当前的翻页数。parse
方法用正则表达式从响应中提取图片URL,并将它们存入BaiduItem
的image_urls
字段。- 控制翻页,如果当前页面数达到4,则停止爬取。
- 格式化
page_url
以获取下一页的URL,并更新page_num
。 - 使用
scrapy.Request
生成新的请求来爬取下一页。
Pipelines文件
1 | from itemadapter import ItemAdapter |
导入所需的模块和类,包括Scrapy的内置
ImagesPipeline
。定义了继承自
ImagesPipeline
的BaiduPipeline
类。num
用于生成重命名后的图片文件名。重写
item_completed
方法,该方法在item的图片被下载完成后调用。遍历下载的图片路径,并重命名每张图片,使用
num
作为新的文件名。
- 标题: Scrapy
- 作者: Yiuhang Chan
- 创建于 : 2021-10-28 13:12:54
- 更新于 : 2024-02-28 18:50:35
- 链接: https://www.yiuhangblog.com/2021/10/28/20211128scrapy/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。