爬虫篇:Ajax请求处理技术总结(下)—— 模拟浏览器行为
2018-04-03 19:16
465 查看
每篇一句:
Never lie to someone who trust you. Never trust someone who lies to you.前言:
上一篇文章中我们介绍了爬取动态网页的一种方式:逆向工程 。这种方式不足之处就是:这种方式要求我们对JavaScript以及Ajax有一定的了解,而且当网页的JS代码混乱,难以分析的时候,上述过程会花费我们大量的时间和精力。
这时候,如果对爬虫执行效率没有过多要求,又不想浪费太多时间在了解JavaScript代码逻辑、找寻Ajax请求链接上,我们可以尝试以下思路:
模拟浏览器行为,通过使用浏览器渲染引擎对目标网页执行JavaScript代码,并解析HTML。
Selenium 简介:
Selenium 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持市面上几乎所有的主流浏览器。本来打算使用的是selenium + PhantomJS的组合,结果编写代码时发现从17年7、8月份,selenium已经不再支持PhantomJS,还好Chrome以及FireFox也相继推出无头 ( headless ) 浏览器模式,本文采用的是Selenium+Chrome的组合。
接下来会结合一个具体网页介绍Selenium的使用。
示例:
还是以 新浪读书_书摘 为例:由于Selenium使用较为简单,直接看示例代码,代码中含有足够的注释:
# coding=utf8 import time from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.common.exceptions import NoSuchElementException # 解析列表页内容 def getItem(): # 处理文章详情页面,具体方法根据具体情况添加 global location # 每解析一篇文章location加一 location += 1 def getLi 4000 st(): global location global driver # 解析网页内容 divs = driver.find_elements_by_class_name("item") # 构造动作链 actions = ActionChains(driver) for i in range(location, len(divs)): div = divs[i] # 标题 title = div.find_element_by_tag_name("a").text # URL url = div.find_element_by_tag_name("a") # url = div.find_element_by_tag_name('a').get_attribute("href") # 进入文章详情页 actions.click(url) actions.perform() actions.reset_actions() # 将driver转移到当前页面,接下来处理文章详情页面 driver.switch_to.window(driver.window_handles[1]) # 在此处调用处理文章详情页的方法,获取所需要的字段 getItem() driver.close() driver.switch_to.window(driver.window_handles[0]) print driver.title # 判断“更多书摘”按钮是否存在,存在的话,返回元素 def loadMore(): global driver elem = None try: elem = driver.find_element_by_id("subShowContent1_loadMore") except NoSuchElementException: pass # 如果不存在或不可见,返回None if elem is None or not(elem.is_displayed()): return None else: return elem # 每两次“更多书摘”后,会出现“下一页”按钮,判断"下一页"是否存在,存在的话,返回元素 def pagebox_next(): global driver next_page = None try: next_page = driver.find_element_by_class_name("pagebox_next") except NoSuchElementException: pass # 如果不存在或不可见,返回None if next_page is None or not(next_page.is_displayed()): return None else: return next_page # 判断是否还有新的内容 def haveNext(): global location global driver more = loadMore() if more: # 是否存在“更多书摘”按钮 return more else: # 无论是否还有“下一页”,都将location置为0 location = 0 more = pagebox_next() return more # 点击“更多书摘”后,之前内容并不会消失,所以设置一个变量记录当前位置 # 每次点击“下一页后,再次置为0 location = 0 # 指定driver的浏览器 # 创建Chrome的无头浏览器 # opt = webdriver.ChromeOptions() # opt.set_headless() # driver = webdriver.Chrome(options=opt) # 创建可见的Chrome浏览器 driver = webdriver.Chrome() # 设置浏览器的隐式等待时间 driver.implicitly_wait(30) # get方法打开网页 driver.get("http://book.sina.com.cn/excerpt/") # 分析文章列表 getList() # 初始化动作链 actions_more = ActionChains(driver) # 判断是否还有新的内容 more_page = haveNext() while more_page: # 添加点击动作 actions_more.click(more_page) actions_more.perform() actions_more.reset_actions() # 每次翻页或刷新页面时要等待页面加载完成, # 这里采用的是强制等待,效率较慢,不推荐这种做法; # 比较适合的是采用Selenium的显性等待方法:"WebDriverWait",配合该类的until()和until_not()方法,就能够根据判断条件而进行灵活地等待了。 time.sleep(3) # 分析加载的新内容,从location开始 getList() # 是否存在更多或下一页 more_page = haveNext() # 关闭浏览器 driver.close()
如果你想实际运行上述代码,请在运行之前确定(win10):
安装了Chrome浏览器(较新版本)。
安装了Chrome浏览器驱动(chromedriver.exe),并正确的添加到了环境变量
path中。
安装了Selenium。
简要解释:
Selenium 使用过程大致如下:创建一个到浏览器的连接
from selenium import webdriver driver = webfriver.Chrome() # 创建无头浏览器方式: # opt = webdriver.ChromeOptions() # opt.set_headless() # driver = webdriver.Chrome(options=opt)
设置等待时间:
driver.implicitly_wait(30)
- 此处是Selenium的隐式等待设置,我们设置了 30秒的延时,如果我们要查找的元素没有出现,Selenium 至多等待30秒,然后就会抛出异常。推荐配合显示等待一起使用 - 显示等待:"WebDriverWait",配合该类的until()和until_not()方法,就能够根据判断条件而进行灵活地等待了。
调用
get()方法加载网页:
driver.get("http://book.sina.com.cn/excerpt/")
从网页获取需要的元素:
driver.find_element_by_id("item")
创建一条动作链:
actions = ActionChains(driver)
为动作链添加动作:
actions.click(url)
动作链执行:
actions.perform()
调用
close()方法关闭浏览器:
driver.close()
之前的示例代码中包含了 翻页的逻辑判断,以及列表页与文章详情页之间的切换, 所以看起来可能比较复杂,但其实执行过程大致就是这个思路。
另外,示例代码并不是一个完整的爬虫,跳转到文章详情页之后,没有进行数据的获取。如果读者有需要的话,可以在示例代码中的
getItem()函数中添加具体的实现,之后还可以补充数据的储存等功能。
分析与比较:
使用Selenium特别要注意一点:使用Selenium时,难免要进行 刷新或页面的切换,这时就要注意网页的响应时间。selenium不会等待网页响应完成再继续执行代码,它会直接执行。二者应该是不同的进程。这里可以选择设置隐性等待和显性等待。
隐形等待:是设置了一个最长等待时间,如果在规定时间内网页加载完成,则执行下一步,否则一直等到时间截止,然后抛出异常。需要特别说明的是:隐性等待对整个driver的周期都起作用,所以只要设置一次即可。
显性等待: WebDriverWait,配合该类的until()和until_not()方法,就能够根据判断条件而进行灵活地等待了。主要的意思就是:程序每隔xx秒看一眼,如果条件成立了,则执行下一步,否则继续等待,直到超过设置的最长时间,然后抛出TimeoutException。
如果同时设置了隐形等待和显性等待的话,在WebDriverWait中显性等待起主要作用,在其他操作中,隐形等待起决定性作用,要注意的是:最长的等待时间取决于两者之间的大者。
与“逆向工程”的比较:
前者(逆向工程)在运行上更快,开销更小。在实际情况中,大多数网页都可以被逆向,但是有些网页足够复杂逆向要花费极大的功夫。
后者(模拟浏览器行为)在思路上更直观,更容易被接受和理解。浏览器渲染引擎能够为我们节省了解网站后端工作原理的时间,但是渲染网页增大了开销,使其比单纯下载HTML更慢。另外,使用后者通常需要轮训网页来检查是否已经得到所需的HTML元素,这种方式非常脆弱,在网络较慢时经常会失败。
采用哪种方法,取决于爬虫活动中的具体情况:
易于逆向、对速度和资源要求高的,应使用前者;
难以逆向、没有工程上的优化的要求的,可以使用后者。
个人认为模拟浏览器的方法应尽量避免,因为浏览器环境对内存和CPU的消耗非常多,可以作为短期决绝方案,此时长期的性能和可靠性并不算重要;而作为长期解决方案,我会尽最大努力对网站进行逆向工程。
最后:
本文介绍了一种一个用于Web应用程序测试的工具Selenium,以及如何在不对网页进行逆向过程的情况下进行数据获取的思路。如果读者想要深入了解Selenium相关内容,这里推荐一个知乎专栏 【每周一个小项目】 其中有六篇文章对Selenium常用的一些类、方法、API进行了介绍还有部分官方文档的翻译,写的比较完善。
另外的参考资料:
Python selenium —— 一定要会用selenium的等待,三种等待方式解读 —— 灰蓝的博客
使用Selenium模拟浏览器,实现自动爬取数据 —— 简书
如果文中有什么不足或错误之处,欢迎指出!
相关文章推荐
- 爬虫篇:Ajax请求处理技术总结(上)——Ajax简介
- 爬虫篇:Ajax请求处理技术总结(中) —— 逆向工程
- 安卓版微信自带浏览器和IE6浏览器ajax请求abort错误处理
- Java 获得网页源代码和模拟浏览器请求(个人总结)
- 安卓版微信自带浏览器和IE6浏览器ajax请求abort错误处理
- 由Ajax请求一般处理程序下载文件引发的问题后的一些总结
- 模拟IIS处理浏览器的请求
- 安卓版微信自带浏览器和IE6浏览器ajax请求abort错误处理
- ajax 请求去除浏览器缓存处理
- Ajax技术之与服务器通信-发送请求与处理响应
- ajax请求之后,后台代码没有处理,浏览器控制台都没有报错,状态码变成302的原因
- java模拟浏览器发送请求并处理响应
- ajax请求总是不成功?浏览器的同源策略和跨域问题详解
- Ajax中自定义发送请求和处理响应对象
- ajax请求400 bad request 个人总结
- 想抛就抛:Application_Error中统一处理ajax请求执行中抛出的异常
- 防重复请求处理的实践与总结
- ajax处理全过程总结
- 当 jquery 发送 ajax 请求的时候遇到服务端session过期超时返回 302 跳转登陆页面的时候怎么办的处理方法
- 使用Spynner基于Webkit从最底层模拟浏览器行为