您的位置:首页 > 其它

爬虫篇: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模拟浏览器,实现自动爬取数据 —— 简书

如果文中有什么不足或错误之处,欢迎指出!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: