使用 Scrapy + Selenium 爬取动态渲染的页面
在通过 scrapy 框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载的情况发生,如果直接使用 scrapy 对其 url 发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行 url 请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在 scrapy 也获取动态加载出的数据,则必须使用 selenium 创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值. 本文分享 scrapy 的介绍和如何配合 selenium 实现动态网页的爬取。
Scrapy 是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。其最初是为了 页面抓取 (更确切来说, 网络抓取 ) 所设计的, 也可以应用在获取 API 所返回的数据 (例如 Amazon Associates Web Services ) 或者通用的网络爬虫。
project_folder -- 项目文件夹名称
|
|──project_name -- 该项目的python模块,一般和项目文件夹名称相同
| |
| |──spider -- 放置spider代码的包,以后所有的爬虫,都存放在这个里面
| |
| |──items.py -- 用来存放爬虫怕写来的数据的模型
| |
| |──middlewares.py -- 用来存放各种中间件的文件
| |
| |──pipelines.py -- 用来对items里面提取的数据做进一步处理,如保存到本地磁盘等
| |
| |──settings.py -- 本爬虫的一些配置信息(如请求头、多久发送一次请求、ip代理池等)
|
|──scrapy.cfg -- 项目的配置文件
Scrapy Engine : Scrapy 框架的核心,负责在 Spider 和 Item Pipeline、Downloader、Scheduler 中间通信、传输数据等。
Spider : 发送需要爬取的链接给引擎,最后引擎把其他模块请求回来的数据再发给爬虫,爬虫就去解析想要的数据。这部分是我们开发者自己写的,因为要爬取哪些链接,页面中的哪些数据是我们需要的,都是由程序员自己决定。
Scheduler : 复制接收引擎发送过来的请求,并按照一定的方式进行排列和整理,负责调度请求的顺序等。
Downloader : 负责接收引擎传过来的下载请求,然后去网络上下载对应的数据在交还给引擎。
item Pipeline: 负责将 Spider (爬虫) 传递过来的数据进行保存,具体保存在哪里,应该看开发者自己的需求。
downloader Middlewares : 可以扩展下载器和引擎之间通信功能的中间件。
Spider Middlewares : 可以扩展引擎和爬虫之间通信功能的中间件。
Scrapy 中的数据流由执行引擎控制,其过程如下:
Selenium 有很多东西,但从本质上讲,它是一个 Web 浏览器自动化工具集,它使用可用的最佳技术远程控制浏览器实例并模拟用户与浏览器的交互。它允许用户模拟最终用户执行的常见活动;在字段中输入文本,选择下拉值和复选框,并单击文档中的链接。它还提供了许多其他控件,例如鼠标移动、任意 JavaScript 执行等等。
安装 pip install selenium
使用 selenium 驱动 chrome 浏览器需要下载 chromedriver,而且 chromedriver 版本需要与 chrome 的版本对应,版本错误的话则会运行报错。
下载 chromedriver 可以通过淘宝镜像地址:http://npm.taobao.org/mirrors/chromedriver/ 。最新的镜像与 Chrome 同名,尽量选择版本相近的避免兼容问题,镜像下 notes.txt 可查看当前驱动支持的版本。
Opera:http://npm.taobao.org/mirrors/operadriver/
IE:http://selenium-release.storage.googleapis.com/index.html(版本号要与 selenium 的版本一致,查看安装的 selenium 版本,可通过 pip show selenium)如果没有 vpn 可能会打不开,可点击下载 3.14.0 版本的。
import requests
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36',
'Accept-Encoding': 'gzip',
'Connection': 'keep-alive',
'Host': 'www.aqistudy.cn',
}
url = "https://192.168.1.1/aqistudy/monthdata.php?city=北京"
res = requests.get(url, headers=header)
if res.status_code == 200:
print("请求成功")
with open("aqistudy.txt", encoding="utf8", mode='w+') as f:
f.write(res.text)
else:
print("请求失败")
<table width="100%" class="table table-condensed table-bordered table-striped table-hover table-responsive" style="margin-bottom:5px">
<tr height=38px>
<th style="background:#d9edf7">月份</th>
<th style="background:#d9edf7">AQI</th>
<th style="background:#d9edf7" class="hidden-xs">范围</th>
<th style="background:#d9edf7" width="80px" >质量等级</th>
<th style="background:#d9edf7">PM2.5</th>
<th style="background:#d9edf7">PM10</th>
<th style="background:#d9edf7" class="hidden-xs">SO2</th>
<th style="background:#d9edf7" class="hidden-xs">CO</th>
<th style="background:#d9edf7" class="hidden-xs">NO2</th>
<th style="background:#d9edf7" class="hidden-xs">O3</th>
</tr>
</table>
var muwSVVIti = 'GETMONTHDATA';
endebug(false, function () {
document.write('检测到非法调试, 请关闭调试终端后刷新本页面重试!');
document.write("<br/>");
document.write("Welcome for People, Not Welcome for Machine!");
diwAafiP = true;
});
txsdefwsw();
document.onkeydown = function() {
if ((e.ctrlKey) && (e.keyCode == 83)) {
alert("检测到非法调试,CTRL + S被管理员禁用");
return false;
}
}
document.onkeydown = function() {
var e = window.event || arguments[0];
if (e.keyCode == 123) {
alert("检测到非法调试,F12被管理员禁用");
return false;
}
}
document.oncontextmenu = function() {
alert('检测到非法调试,右键被管理员禁用');
return false;
}
}
可以看到 返回的页面源码中 只有一个天气表格的框架, 没有我们需要的天气信息. 而且出现了被检测的信息. 出现这种情况 是因为:
● 目标网页是动态渲染的页面, 所以我们只能看到天气表格的框架 看不到具体的信息
● 目标网页检测到 selenium 禁止调试
1、运行一个 Scrapy 的项目
2、编写 spirder, 演示文件所以我们不具体爬取某个数据, 这里全量保存源代码
import scrapy
class ApistudyMainSpider(scrapy.Spider):
name = 'apistudy_main'
allowed_domains = ['192.168.1.1']
def start_requests(self):
start_url = r'https://192.168.1.1/aqistudy/monthdata.php?city=北京'
yield scrapy.Request(url=start_url, callback=self.parse, dont_filter=True)
def parse(self, response):
yield {'text': response.text}
3、编辑 piplines.py 用来保存我们爬取的数据
class AqistudyPipeline(object):
# def process_item(self, item, spider):
# return item
def open_spider(self, spider):
self.file = open('my.html', 'w', encoding='utf-8')
def close_spider(self, spider):
self.file.close()
def process_item(self, item, spider):
self.file.write(str(item['text']))
4、编辑中间件, 规避反扒和使用 selenium 渲染页面
class RandomHeaderMiddleWare:
def __init__(self):
self.user_agents = USER_AGENTS
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(self.user_agents)
# 如果IP被限制, 可以在此下载中间件添加代理
option = webdriver.ChromeOptions()
option.add_argument('--headless') # 无界面运行
option.add_argument('--disable-gpu') # 禁止gpu加速
option.add_argument("no-sandbox") # 取消沙盒模式
option.add_argument("disable-blink-features=AutomationControlled") # 禁用启用Blink运行时的功能
option.add_experimental_option('excludeSwitches', ['enable-automation']) # 开发者模式
driver = webdriver.Chrome(options=option)
# 移除 `window.navigator.webdriver`. scrapy 默认为True
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
driver.get(request.url)
driver.implicitly_wait(5)
content = driver.page_source
driver.quit()
return HtmlResponse(request.url, encoding="utf-8", body=content, request=request)
def process_response(self, request, response, spider):
return response
def process_exception(self, request, exception, spider):
return None
5、在 settings.py 添加中间件
SPIDER_MIDDLEWARES = {
'aqistudy.middlewares.AqistudySpiderMiddleware': 543,
}
DOWNLOADER_MIDDLEWARES = {
'aqistudy.middlewares.AqistudyDownloaderMiddleware': 543,
'aqistudy.middlewares.RandomHeaderMiddleWare': 545, # 添加下载中间件
}
ROBOTSTXT_OBEY = False
6、运行 Scrapy 结果文件将通过 piplines.py
保存至文件. 下面是爬取下来的天气页面源代码中的表格信息
<table width="100%" class="table table-condensed table-bordered table-striped table-hover table-responsive"
style="margin-bottom:5px">
<tbody>
<tr height="38px">
<th style="background:#d9edf7">月份</th>
<th style="background:#d9edf7">AQI</th>
<th style="background:#d9edf7" class="hidden-xs">范围</th>
<th style="background:#d9edf7" width="80px">质量等级</th>
<th style="background:#d9edf7">PM2.5</th>
<th style="background:#d9edf7">PM10</th>
<th style="background:#d9edf7" class="hidden-xs">SO2</th>
<th style="background:#d9edf7" class="hidden-xs">CO</th>
<th style="background:#d9edf7" class="hidden-xs">NO2</th>
<th style="background:#d9edf7" class="hidden-xs">O3</th>
</tr>
<tr>
<td align="center"><a href="daydata.php?city=北京&month=2013-12">2013-12</a></td>
<td align="center">100</td>
<td align="center" class="hidden-xs">23~291</td>
<td align="center"><span
style="display:block;width:60px;text-align:center;background-color:#efdc31;color:black;">轻度污染</span>
</td>
<td align="center">73</td>
<td align="center">97</td>
<td align="center" class="hidden-xs">37</td>
<td align="center" class="hidden-xs">1.73</td>
<td align="center" class="hidden-xs">56</td>
<td align="center" class="hidden-xs">38</td>
</tr>
<tr>...>
<tr>...>
<tr>...>
<tr>...>
<tr>...>
<tr>...>
<tr>...>
....
</tbody>
</table>
表格中天气的具体信息已经被渲染
在撰写爬虫程序时, 遇到动态渲染的页面我们可以使用 Scrapy+Selenium 对页面规避反爬策略和爬取页面信息. 虽然 webdriver 影响到了 Scrapy 的运行速度, 我们还可以使用 scrapy-redis 让我们的爬虫变成分布式以提高效率。