irpas技术客

【Web漏洞扫描这件事】爬虫2-scrapy框架_Web3Ao

未知 5081

文章目录 《Python 网络爬虫 Scrapy框架》第一章解析HTML的库解析XML/HTML的语法多层级网页爬取逻辑保存数据和展示数据 第二章垂直搜索爬虫安装Scrapy爬虫框架创建并启动Scrapy爬虫工程整体把握scrapy工程爬虫请求流程审计 scrapy 框架 第三章:数据提取和多层级网页爬取明确进度和目标Response 对象核心1:xpath选择器和css选择器核心2:多层级网页爬取(主要问题的解决) 定制爬虫的主要问题解决迭代的问题定位元素问题 第四章:数据保存到csv文件Feed exports 定制请求添加 Cookie

《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当前返回页面对应的urlstatusHTTP请求状态码meta用于在 request 和 response 之间传递数据bodyHTTP请求的返回数据(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有表单收集表单,没表单搜集 GetParams如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&amp;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)


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #文章目录Python #网络爬虫 #scrapy框架