Web漏洞扫描这件事爬虫2-scrapy框架
Posted 南瓜__pumpkin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Web漏洞扫描这件事爬虫2-scrapy框架相关的知识,希望对你有一定的参考价值。
文章目录
《Python 网络爬虫 Scrapy框架》
全文主要参考《Python 网络爬虫 Scrapy框架》- 肖睿 陈磊 / 主编 - 刘信杰 王莹莹 秦丽娟 / 副主编,人民邮电出版社。
书籍章节目录如下
通读前两章,以及书籍章节目录,前4章可以拿来用,后续章节可能会用到的有 Scrapy反反爬技术、分布式爬虫Scrapy+Redis,后续的将来再说,把握当下。
第一章
把握4个概念:lxml库、xpath语法、多层级网页爬取逻辑、数据保存和展示。
解析html的库
请求网页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解析
解析XML/HTML的语法
xpath是一套用于解析XML/HTML的语法,使用 路径表达式
选取xml/html中的节点集。值得一提的是,编写xpath需要熟悉html的元素。
多层级网页爬取逻辑
网页嵌套问题:抓取一个网页的数据容易,但如何抓取该网页嵌套的网页?即让爬虫有规律地抓取 所有嵌套的详情页
。
这种情况涉及爬虫的多层级网页爬取逻辑,以两层网页为例整理爬取逻辑:
- 请求第一层网页(目标数据是嵌套页的链接)
- 使用xpath解析网页,获取嵌套页的链接
- 遍历嵌套页链接,逐一发送请求
- 使用xpath解析第二层网页,获取最终目标数据
- 保存目标数据
此处应有爬虫算法,比如 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 文件。
一天即将结束,回顾当天并没有实质性的成果,写出爬虫才是目标,其它的都是假的。
第二章
垂直搜索爬虫
根据使用场景,把爬虫分为通用搜索爬虫、垂直搜索爬虫,其中垂直搜索爬虫在运行时尽量只抓取与需求相关的网页信息,这里主要使用垂直搜索爬虫。
安装Scrapy爬虫框架
参考 Windows环境安装Scrapy框架步骤,本地执行命令 pip3 install scrapy 一步到位了
创建并启动Scrapy爬虫工程
开发 Scrapy 爬虫工程,要使用 Scrapy 命令创建爬虫工程,该工程是半成品爬虫项目。在命令行创建爬虫工程的步骤如下
- 创建爬虫工程:scrapy startproject 工程名
- 切换到工程根目录
- 创建爬虫文件:scrapy genspider 爬虫名 起始URL
- 启动爬虫工程:scrapy crawl 爬虫名
#创建爬虫项目并运行爬虫
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 scrapy
class 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工程
创建项目的文件结构如下,过滤了项目配置文件 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 模块承担了设置爬虫行为模式、模块启用等配置功能,在爬虫框架中是非常重要的模块。列举部分在开发中常用的配置:
- pipeline模块的启动以及启用顺序配置
- spider爬取网页数据的频率、默认Headers等属性配置
- 启动或关闭指定的spider middleware配置、downloader middleware配置
爬虫请求流程
结合第三章的部分内容,列出执行流程。
# 流程图代码
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 框架的源码。
审计 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 对象
爬虫访问网页后,返回包含网页结果的 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 数据。
核心1:xpath选择器和css选择器
xpath 选择器基于 lxml 开发,Scrapy 爬虫框架支持 xpath 选择器提取网页数据,将选择器的接口整合到了 Response 类中。
使用 lxml 定位网页元素,无需其他操作即可通过返回值获得对应数据,但 Scrapy 爬虫框架使用 xpath 表达式定位网页元素后,方法返回值的对象是 Selector 。要想获取真正的目标数据,还要调用 extract() 方法提取数据。
使用 extract() 方法从 Selector 提取数据时需要注意 3 点(提高程序健壮性):
- extract() 方法返回值类型是 列表 ,还需要通过列表索引获取指定数据
- 空列表时,调用索引会引发程序异常
- extract_first()可以获取第一个匹配的元素,没有元素时会返回 none
xpath 常用语法如表,但组合非常灵活,需要结合实例不断练习
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从根节点选取 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的为自豪 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
核心2:多层级网页爬取(主要问题的解决)
爬取网站数据除了设置入口网页 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: <class 'bs4.BeautifulSoup'>
form_list = soup.find_all('form') # 返回一个列表
input_list = soup.find_all('input')
a_list = soup.find_all('a')
param_list = []
a_href = []
# 只处理单表单,有多个表单给出提示即可。2个表单变量
form_info = str(len(form_list)) + "个表单"
# 值得注意,input 也有链接,比如按钮跳转的网页。根据有无name属性,区分成两种数据:参数和链接
for input in input_list:
if input.get('name') and "submit" not in str(input).lower():
param_list.append(input.get('name'))
print(form_info, response.url, param_list, response.status, soup.title, len(response.body))
# 保存到 csv 文件
# 获取所有超链接,检查后决定是否遍历
complete_a = ""
for a in a_list:
url = a.get('href')
if "http" in url and self.start_url not in url: # 判断页面为无关
continue
elif url == ".":
continue
else:
if start_url in a.get('href'): # 绝对路径
a_href.append(url)
complete_a = url
else: # 相对路径
a_href.append(start_url + url)
complete_a = start_url + url
if complete_a not in self.accessed_href:
self.accessed_href.append(complete_a)
# print(complete_a)
yield scrapy.Request(url=complete_a, dont_filter=True, cookies=self.cookie)
定位元素问题
天下苦 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 | <form method=""><input type="" name=""> |
sql-labs | 该靶场前端没有参数信息,只有文字hint,有时候会是这样,比如旅馆id页的遍历 |
pikachu | <form method=""><select name=“id”><input type="" name=""> |
bwapp | <form method=""><input type="" name=""> |
如何定位 form 表单?xpath 定位需要详细的位置信息,而 form 标签的层级和值都是未知。考虑 xpath 、 regular expression 、bs4之后,选择 Python 的库 bs4 来定位 form 标签。
参考 python3解析库BeautifulSoup4,Python爬虫数据提取方式——使用bs4提取数据。首先查找 form 标签,然后从表单中筛选出 method 和 params,最后保存或者打印即可。(一个网页最好只有一个表单,但可以拥有多个表单,默认一个表单,多表单则提示)
Input类型:不能定位所有的 input 标签,因为有可能是超链接按钮,比如 DVWA 的某页代码如下。当前有参数、按钮两种类型。
<input type="button" value="View Help" class="popup_button" id="help_button" data-help-url="../../vulnerabilities/view_help.php?id=upload&security=impossible" )"="">
parse()
提取数据部分代码如下,
def parse(self, response):
# 只处理单表单,有多个表单给出提示即可。2个表单变量
soup = BeautifulSoup(response.body, 'lxml') # type: <class 'bs4.BeautifulSoup'>
form_list = soup.find_all('form') # 返回一个列表
form_info = "一个表单"
if len(form_list) > 1:
form_info = str(len(form_list)) + "个表单"
# 值得注意,input 也有链接,比如按钮跳转的网页。根据有无name属性,区分成两种数据:参数和链接
param_list = []
input_list = soup.find_all('input')
for input in input_list:
if (input.get('name')) and (input.get('name') != "Submit"):
param_list.append(input.get('name'))
print(form_info, response.url, param_list, response.status, soup.title, len(response.body))
第四章:数据保存到csv文件
Feed exports
保存数据的两种方式:爬取网站信息后,还需要将爬取的数据保存,给后续的数据分析等工作提供数据资源。保存数据的方式可以分为两大类:直接保存在文件中、保存到数据库中。文件格式以 Csv 和 Json 居多,这两种格式是数据分析领域常用的文件格式,能够结构化地保存爬取地数据,有利于数据读取与分析工作。
定制
请求添加 Cookie
设置Cookie的方式,参考 scrapy中如何设置应用cookies
# 重写 start_requests()方法,测试成功
def start_requests(self):
start_urls = ['http://127.0.0.1/DVWA-master//']
cookies = {
"security": "impossible",
"PHPSESSID": "9nvum4l9mpfaejgjqljlvdqpp0",
}
proxies = {'http': 'http://localhost:8080', 'https': 'http://localhost:8080'}
for url in start_urls:
yield scrapy.Request(url=url, cookies=cookies)
以上是关于Web漏洞扫描这件事爬虫2-scrapy框架的主要内容,如果未能解决你的问题,请参考以下文章
第三百三十三节,web爬虫讲解2—Scrapy框架爬虫—Scrapy模拟浏览器登录—获取Scrapy框架Cookies