全文主要参考《Python 网络爬虫 Scrapy框架》- 肖睿 陈磊 / 主编 - 刘信杰 王莹莹 秦丽娟 / 副主编,人民邮电出版社。
书籍章节目录如下
通读前两章,以及书籍章节目录,前4章可以拿来用,后续章节可能会用到的有 Scrapy反反爬技术、分布式爬虫Scrapy+Redis,后续的将来再说,把握当下。
把握4个概念:lxml库、xpath语法、多层级网页爬取逻辑、数据保存和展示。
请求网页URL并得到 HTML 源码后,需要解析 HTML 源码得到想要的数据,lxml库就是解析html的一个第三方库。
Python标准库自带xml模块,能解析xml文件和html页面内容,但性能和接口不够人性化。而第三方库lxml用Cython实现,增加很多使用功能,其大部分功能存在 lxml.etree 中。
使用 lxml 库提取网页内容步骤如下:
导入相应类库:from lxml import etree
使用HTML()方法生成待解析对象:tree=etree.HTML(html),其中html是目标页面的html源码
调用待解析对象的xpath()方法 tree.xpath(),填入xpath语句作为参数进行html解析
xpath是一套用于解析XML/HTML的语法,使用 路径表达式
选取xml/html中的节点集。值得一提的是,编写xpath需要熟悉html的元素。
网页嵌套问题:抓取一个网页的数据容易,但如何抓取该网页嵌套的网页?即让爬虫有规律地抓取 所有嵌套的详情页
。
这种情况涉及爬虫的多层级网页爬取逻辑,以两层网页为例整理爬取逻辑:
此处应有爬虫算法,比如 Scrapy 使用的深度优先算法,数据结构学了的,见 浅谈网络爬虫中深度优先算法和简单代码实现。
把 URL-Method-Params-Status-Title-Length 格式的数据保存成 csv 文件,或者 txt 文件?后面还有处理考虑的问题,需要对这些目标资源进行分类吗?不分类数据量过大,除非压缩每个url的请求量,要么就分类,这些是fuzz时需要考虑的事情。
当下只需要考虑,保存数据的可读性,具体点就是阅读的舒适性。后期fuzz可能会保存为txt或csv,但目前我个人更想保存成 py,因为编辑器读起来舒适。
参考 csv、txt和tsv数据文件的异同点,以及如何使用Python读取和生成:通常来说,为了更好的用多种语言处理数据,推荐将数据存为csv格式(csv文件是以逗号分隔的一个文本文件,可以直接更改后缀为与其他类型文件),可同时在excle、python、matlab、sas和R等语言中切换自由简易,数据格式不受损!
选择:把 URL-Method-Params-Status-Title-Length 格式的数据保存成 csv 文件。
一天即将结束,回顾当天并没有实质性的成果,写出爬虫才是目标,其它的都是假的。
根据使用场景,把爬虫分为通用搜索爬虫、垂直搜索爬虫,其中垂直搜索爬虫在运行时尽量只抓取与需求相关的网页信息,这里主要使用垂直搜索爬虫。
参考 Windows环境安装Scrapy框架步骤,本地执行命令 pip3 install scrapy 一步到位了
开发 Scrapy 爬虫工程,要使用 Scrapy 命令创建爬虫工程,该工程是半成品爬虫项目。在命令行创建爬虫工程的步骤如下
#创建爬虫项目并运行爬虫
scrapy startproject helloSpider
cd helloSpider
scrapy genspider test http://127.0.0.1/DVWA-master/
scrapy crawl test # 工程根目录执行#执行回显模块
[scrapy.utils.log]
[scrapy.crawler]
[scrapy.extensions.telnet]
[scrapy.middleware]
[scrapy.core.engine]
[scrapy.extensions.logstats]
[scrapy.downloadermiddlewares.retry]
[scrapy.downloadermiddlewares.robotstxt]# helloSpider/spiders/test.py文件
import scrapyclass TestSpider(scrapy.Spider):name = 'test'allowed_domains = ['http://127.0.0.1/DVWA-master/']start_urls = ['http://http://127.0.0.1/DVWA-master//']def parse(self, response):pass
部分执行结果如下,可以看到打印内容有些混乱。
创建项目的文件结构如下,过滤了项目配置文件 scrapy.cfg,因为开发爬虫时无需改动该文件。如图 Scrapy 爬虫工程主要由5个模块组成:爬虫、items模块、中间件模块、数据保存模块、配置模块,接下来简单说明各个模块。
Scrapy 框架的引擎和调度器暂时忽略
#流程图代码
graph TB
A(项目根目录helloSpider)
B(helloSpider)
B3(spiders)
B4(items.py)
B5(middlewares.py)
B6(pipelines.py)
B7(settings.py)
A-->B
B-->B3
B-->B4
B-->B5
B-->B6
B-->B7
b31(test.py)
B3-->b31
(1)spiders 文件夹:是 Scrapy 爬虫工程的爬虫模块,可以有多个爬虫文件。设计理念是方便一站一爬虫,而爬虫可以共用工程中的其它模块,提高复用和开发效率。
爬虫文件的内容显而易见,值得注意的是该爬虫默认无状态,因此 Scrapy 框架允许在爬虫文件中通过重写 start_requests()方法自定义对爬虫起始页面的爬取设置。
(2)items.py 文件:爬取的数据有可能需要在各模块之间传输,因此在 items.py 中定义统一的数据格式。
(3)pipelines.py 文件:Pipeline是管道的意思,是框架中的数据处理模块,常用于完成数据的持久化工作,还可以实现爬取数据的过滤、去重等工作。
(4)middlewares.py 文件:Middlewares 是中间件的意思,中间件的存在方便了爬虫框架的功能扩展。在 Scrapy 爬虫框架中 middleware 分为 downloader 、spider两类。
(5)settings.py 文件:Settings 模块承担了设置爬虫行为模式、模块启用等配置功能,在爬虫框架中是非常重要的模块。列举部分在开发中常用的配置:
结合第三章的部分内容,列出执行流程。
# 流程图代码
graph TB
a(启动爬虫)
b(引擎访问start_urls获取URL)
c(引擎调用Downloader模块下载网页)
d(引擎把下载结果传递给Spider的parse方法)
e(用户处理Response类对象response提取数据)
a-->b
b-->c
c-->d
d-->e
接下来第三章内容,终于要涉及到编写爬虫和落地的问题了(数据处理和数据保存)
目前了解 scrapy 工程的执行流程,但不清楚 scrapy 引擎是如何运行的、scrapy 的入口文件是哪一个、尤其是如何定义的请求类,这些对二开非常重要。所以接下来,审计一下 scrapy 框架的源码。
审计先放着,主要目标是定制爬虫,别迷失在知识海洋。
框架文件和入口文件,显而易见入口文件是 __main__.py
文件:
请求类 Request:在 scrapy 根目录下看到 http 文件夹,该目录下的 __init__.py
文件定义请求类:class Request(object_ref)
scrapy 通过 Request 类对象发起请求,参考:python——scrapy中Request参数。
第二章重写了 start_requests(self) 方法,在 test.py 文件中包含如下变量,会随着目标的改变而改变:
当前爬虫变量 | 说明 |
---|---|
allowed_domains | 允许爬取的网址,不定义时会爬取所有网页,即通用搜索爬虫 |
start_urls | 爬虫起点网页 |
COOKIEs | 身份验证 |
在完成了第一个网页的访问后,本章需要完成两个任务:数据的筛选提取、多层级网页爬取。
爬虫访问网页后,返回包含网页结果的 response 对象给 parse() 方法,其常用属性和方法如下表:
属性或方法 | 说明 |
---|---|
url | 当前返回页面对应的url |
status | HTTP请求状态码 |
meta | 用于在 request 和 response 之间传递数据 |
body | HTTP请求的返回数据(html或json),即数据处理的数据源 |
xpath() | 使用 xpath() 选择器解析网页 |
css() | 使用 css 选择器解析网页 |
meta属性:meta 属性存在的原因是,在某些场景下书爬取不是一次就能够完成的,比如爬取淘宝商品时,首先在列表页面爬取商品标题、价格、以及商品详情页面URL,然后使用该 URL 再次爬取商品详情页获取详细描述,合并两次爬取结果才是全部内容。这个需求在 scrapy 爬虫框架中通过 meta 来实现。
body属性:scrapy 爬虫框架提供了自己的 html 解析工具,即 xpath 选择器和 css 选择器,但爬取下来的数据格式并不一定都是 html 。如果网站使用了 前后端分离技术
,则从数据接口爬取下来的数据格式是 Json,此时只能通过 body 属性获取原始数据,然后使用第三方库解析 Json 数据。
xpath 选择器基于 lxml 开发,Scrapy 爬虫框架支持 xpath 选择器提取网页数据,将选择器的接口整合到了 Response 类中。
使用 lxml 定位网页元素,无需其他操作即可通过返回值获得对应数据,但 Scrapy 爬虫框架使用 xpath 表达式定位网页元素后,方法返回值的对象是 Selector 。要想获取真正的目标数据,还要调用 extract() 方法提取数据。
使用 extract() 方法从 Selector 提取数据时需要注意 3 点(提高程序健壮性):
xpath 常用语法如表,但组合非常灵活,需要结合实例不断练习
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从根节点选取 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的为自豪 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
爬取网站数据除了设置入口网页 start_urls 外,还要让爬虫实现自我驱动,按照设定的程序逻辑爬取所有符合条件的网页数据。对此,在使用第三方库开发爬虫时需要通过 循环等流程控制语句
来实现,而在 Scrapy 爬虫框架将以更优雅简便的方式实现 多层级页面 的爬取。
相同结构页面爬取,比如分页站点的数据爬取。
不同结构网页爬取,如分页网页的列表页和详情页的页面结构不同,这是处理列表页面的 parse() 方法不再适用,需要指定新的方法处理详情页的数据。实现很简单,在构造 Request 对象时,添加 callback 参数指定网页下载结果的处理方法即可。
(不设置callback参数时,默认使用 parse() 方法处理)
页面结构不会对南瓜爬虫有影响,只需要考虑定位什么元素,以及访问如何迭代两个问题。补充:还有一个URL的去重问题。
安全开发三大算法问题
:数组去重、页面相似度比较、流程控制。
定制爬虫涉及其中两个问题:数组去重算法筛选URL、流程控制实现多层级网页爬取。
一般爬虫判断网页结构是否相同,会涉及页面相似度算法,而流程控制的问题,Scrapy 爬虫框架帮我们解决了,所以只需要解决 URL 去重问题。
多线程的问题也被 Scrapy 爬虫框架解决了。
相同结构页面爬取中有提到:获取新的URL之后,在 parse() 方法中使用该 URL 构造 Request 对象,然后通过 yield 关键字将 Request 对象作为方法的返回值返回。Request 对象随后会被 Scrapy Engine 获得并转发给 Scheduler 模块进行调度,由 Downloader 模块下载网页代码,最后再传给 parse() 方法以完成新页面的解析。
简单来说,直接在 parse() 中迭代即可。
值得注意的是,网站一般都会有 访问频率限制
,因此需要在 settings.py 文件中添加代码限制爬虫频率:
#设置每次爬取的间隔为 1 s,实际编写中使用通用的大写字母
Download_delay=1
Scrapy 爬虫迭代方式:在 parse()
方法中使用新的 Url 构造 Request 对象。(重写 start_requests方法是对爬虫起始页面的自定义爬取设置)。通过这种迭代方式,如果我们获取了链接 a ,则构造 yield scrapy.Request(url=url)
。
问题一,创建新的 Request 对象,需要重新设置爬虫信息吗,比如 COOKIE ?(待测试)(测试结果是需要)
问题二,在构造之前我们需要检查该链接是否已经爬取过,可以为 class 定义一个列表 accessed_href 专门用来比较。
在迭代的过程中遇到一个问题:通过 yield scrapy.Request(url=complete_a)
进行迭代,发现并没有经过 parse()
处理,甚至难以确定是否访问。参考 关于Python Scrapy框架 yield scrapy.Request(next_url, call_back="")无法翻页情况解决,修改 allowed_domains
,并添加参数 dont_filter=True
,成功解决无法迭代的问题。
完成迭代的 parse()
代码:
def parse(self, response):start_url = "http://127.0.0.1/DVWA-master/"soup = BeautifulSoup(response.body, 'lxml') # type:
天下苦 html 久矣,借阅相关书籍把 html 搞了吧。图书馆搜索两本不错的书如下,机械工业出版社的书一直印象很好,然后引进的翻译书籍主要考虑更加落地底层的设计和原理,所以选择了这两本书,明天借阅到手翻翻就知道效果了。
书名 | 作者 | 出版社 | 出版时间 |
---|---|---|---|
《HTML》 | (美)埃文斯(Evans)著 | 科学出版社 | 1996.7 |
《HTML5基础与实践教程》 | 吕云翔等编著 | 机械工业出版社 | 2020 |
提取数据的目标格式是:URL-Method-Params-Status-Title-Length
,其中
需要考虑的数据 | 说明 |
---|---|
URL | 目前简单看了元素 link 和元素 a,暂时只考虑超链接 a 元素 |
Method | 有表单收集表单,没表单搜集 Get |
Params | 如Method |
关于 Method 参数提交方式(前后端数据交互),参考 Web页面向后台提交数据的方式和选择,主要分为三种:通过表单提交、通过网页链接提交、通过ajax提交。
通过ajax提交:Javascript支持ajax方式创建HTTP请求,可以通过在HTML页面元素的事件处理函数中创建ajax请求,在url参数里携带所需提交的参数,从而提交到后台,这种方式提交后页面不会刷新。
如何提取页面要提交的参数信息?先检测 form 表单,其它方式另开文章学习前端提交数据方式,然后再补充。
靶场 | 说明 |
---|---|
DVWA | |
sql-labs | 该靶场前端没有参数信息,只有文字hint,有时候会是这样,比如旅馆id页的遍历 |
pikachu | |
bwapp |
如何定位 form 表单?xpath 定位需要详细的位置信息,而 form 标签的层级和值都是未知。考虑 xpath 、 regular expression 、bs4之后,选择 Python 的库 bs4 来定位 form 标签。
参考 python3解析库BeautifulSoup4,Python爬虫数据提取方式——使用bs4提取数据。首先查找 form 标签,然后从表单中筛选出 method 和 params,最后保存或者打印即可。(一个网页最好只有一个表单,但可以拥有多个表单,默认一个表单,多表单则提示)
Input类型:不能定位所有的 input 标签,因为有可能是超链接按钮,比如 DVWA 的某页代码如下。当前有参数、按钮两种类型。
<input type&#61;"button" value&#61;"View Help" class&#61;"popup_button" id&#61;"help_button" data-help-url&#61;"../../vulnerabilities/view_help.php?id&#61;upload&security&#61;impossible" )"&#61;"">
parse()
提取数据部分代码如下&#xff0c;
def parse(self, response):# 只处理单表单&#xff0c;有多个表单给出提示即可。2个表单变量soup &#61; BeautifulSoup(response.body, &#39;lxml&#39;) # type:
保存数据的两种方式&#xff1a;爬取网站信息后&#xff0c;还需要将爬取的数据保存&#xff0c;给后续的数据分析等工作提供数据资源。保存数据的方式可以分为两大类&#xff1a;直接保存在文件中、保存到数据库中。文件格式以 Csv 和 Json 居多&#xff0c;这两种格式是数据分析领域常用的文件格式&#xff0c;能够结构化地保存爬取地数据&#xff0c;有利于数据读取与分析工作。
设置COOKIE的方式&#xff0c;参考 scrapy中如何设置应用COOKIEs
# 重写 start_requests()方法&#xff0c;测试成功def start_requests(self):start_urls &#61; [&#39;http://127.0.0.1/DVWA-master//&#39;]COOKIEs &#61; {"security": "impossible","PHPSESSID": "9nvum4l9mpfaejgjqljlvdqpp0",}proxies &#61; {&#39;http&#39;: &#39;http://localhost:8080&#39;, &#39;https&#39;: &#39;http://localhost:8080&#39;}for url in start_urls:yield scrapy.Request(url&#61;url, COOKIEs&#61;COOKIEs)