|
2#
楼主 |
发表于 2018-5-29 15:40:12
|
只看该作者
我们就可以通过这些差异来写等待条件。要想等到航班加载完毕,页面上应该会显示 “共搜索xx个
航班” 这样的文本,而这个文本在 id 为 loadingStatus 的元素中。expected_conditions 提供的类 te
xt_to_be_present_in_element 正满足我们的要求,可以像下面这样:
Wait(browser, 60).until(
Expect.text_to_be_present_in_element((By.ID, "loadingStatus"), u"共搜索")
)
下面是完整的代码,可见一个浏览器爬虫跟传统爬虫比起来还是有些差异的,浏览器爬虫关注点更
多的在页面元素的处理上。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait as Wait
from selenium.webdriver.support import expected_conditions as Expect
browser = webdriver.Chrome(executable_path="./drivers/chromedriver.exe")
browser.get('http://www.tuniu.com/flight/intel/sha-sel')
Wait(browser, 60).until(
Expect.text_to_be_present_in_element((By.ID, "loadingStatus"), u"共搜索")
)
flight_items = browser.find_elements_by_class_name("flight-item")
for flight_item in flight_items:
flight_price_row = flight_item.find_element_by_class_name("flight-price-row")
print(flight_price_row.get_attribute("data-no"))
除了上面提到的 presence_of_element_located 和 text_to_be_present_in_element 这两个等待条件,
Selenium 还提供了很多有用的条件类,参见 Selenium 的 WebDriver API。
二、Selenium 如何使用代理服务器?
通过上一节的介绍,相信你也可以用 Selenium 写一个简单的爬虫了。虽然 Selenium 完全模拟了人工
操作,给反爬增加了点困难,但是如果网站对请求频率做限制的话,Selenium 爬虫爬快了一样会遭遇
被封杀,所以还得有代理。
代理是爬虫开发人员永恒的话题。所以接下来的问题就是怎么在 Selelium 里使用代理,防止被封杀?
我在很久之前写过几篇关于传统爬虫的博客,其中也讲到了代理的话题,有兴趣的同学可以参考一下
Java 和 HTTP 的那些事(二) 使用代理。
在写代码之前,我们要了解一点,Selenium 本身是和代理没关系的,我们是要给浏览器设置代理而不
是给 Selenium 设置,所以我们首先要知道浏览器是怎么设置代理的。浏览器大抵有五种代理设置方式,
第一种是直接使用系统代理,第二种是使用浏览器自己的代理配置,第三种通过自动检测网络的代理
配置,这种方式利用的是 WPAD 协议,让浏览器自动发现代理服务器,第四种是使用插件控制代理配
置,譬如 Chrome 浏览器的 Proxy SwitchyOmega 插件,最后一种比较少见,是通过命令行参数指定
代理。这五种方式并不是每一种浏览器都支持,而且设置方式可能也不止这五种,如果还有其他的方
式,欢迎讨论。
直接使用系统代理无需多讲,这在生产环境也是行不通的,除非写个脚本不断的切换系统代理,或者使
用自动拨号的机器,也未尝不可,但这种方式不够 programmatically。而浏览器自己的配置一般来说基
本上都会对应命令行的某个参数开关,譬如 Chrome 浏览器可以通过 --proxy-server 参数来指定代理:
chrome.exe http://www.ip138.com --proxy-server=127.0.0.1:8118
注:执行这个命令之前,要先将现有的 Chrome 浏览器窗口全部关闭,如果你的 Chrome 安装了代理
配置的插件如 SwitchyOmega,还需要再加一个参数 --disable-extensions 将插件禁用掉,要不然命令
行参数不会生效。
2.1 通过命令行参数指定代理
使用 Selenium 启动浏览器时,也可以指定浏览器的启动参数。像下面这样即可:
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--proxy-server=127.0.0.1:8118')
browser = webdriver.Chrome(
executable_path="./drivers/chromedriver.exe",
chrome_options=chrome_options
)
browser.get('http://ip138.com')
这里的 --proxy-server 参数格式为 ip:port,注意它不支持这种带用户名密码的格式 username:pass
word@ip:port,所以如果代理服务器需要认证,访问网页时就会弹出一个认证对话框来。虽然使用
Selenium 也可以在对话框中填入用户名和密码,不过这种方式略显麻烦,而且每次 Selenium 启动
浏览器时,都会弹出代理认证的对话框。更好的做法是,把代理的用户名和密码都提前设置好,对
于 Chrome 浏览器来说,我们可以通过它的插件来实现。
2.2 使用插件控制代理**
Chrome 浏览器下最流行的代理配置插件是 Proxy SwitchyOmega,我们可以先配置好 SwitchyOm
ega,然后 Selenium 启动时指定加载插件,Chrome 提供了下面的命令行参数用于加载一个或多个
插件:
chrome.exe http://www.ip138.com --load extension=SwitchyOmega
不过要注意的是,--load-extension 参数只能加载插件目录,而不能加载打包好的插件 *.crx 文件,
我们可以把它当成 zip 文件直接解压缩到 SwitchyOmega 目录即可。代码如下:
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--load-extension=SwitchyOmega')
browser = webdriver.Chrome(
executable_path="./drivers/chromedriver.exe",
chrome_options=chrome_options
)
browser.get('http://ip138.com')
另外,Selenium 的 ChromeOptions 类还提供了一个方法 add_extension 用于直接加载未解压的插件文
件,如下:
chrome_options.add_extension('SwitchyOmega.crx')
这种做法应该是可行的,不过我没有具体去尝试,因为这种做法依赖于 SwitchyOmega 的配置,如何在
加载插件之前先把代理都配好?如何运行时动态的切换代理?这对爬虫来说至关重要,以后有时候再
去研究吧。不过很显然,直接使用 SwitchyOmega 插件有点重了,我们能不能自己写一个简单的插件
来实现代理控制呢?
当然可以。而且这个插件只需要两行代码即可。
关于 Chrome 插件的编写,我之前有过两篇博客:我的第一个Chrome扩展:Search-faster 和 我的第二
个Chrome扩展:JSONView增强版,感兴趣的同学可以先看看这两篇了解下如何写一个 Chrome 插件。
这里略过不提,我们这个插件需要有两个文件,一个是 manifest.json 文件,为插件的清单文件,每个
插件都要有,另一个是 background.js 文件,它是背景脚本,类似于后台驻留进程,它就是代理配置插
件的核心。
下面我们就来看看这两行代码,第一行如下:
chrome.proxy.settings.set({
value: {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: "http",
host: "127.0.0.1",
port: 8118
},
bypassList: ["foobar.com"]
}
},
scope: "regular"
}, function() {});
chrome.proxy 是用于管理 Chrome 浏览器的代理服务器设置的 API,上面的代码通过其提供的方法 ch
rome.proxy.settings.set() 设置了一个代理服务器地址,mode 的值为 fixed_servers 表示根据下面的 ru
les 来指定某个固定的代理服务器,代理类型可以是 HTTP 或 HTTPS,还可以是 SOCKS 代理。mode 的
值还可以是 direct(无需代理),auto_detect(通过 WPAD 协议自动检测代理),pac_script(通过 P
AC 脚本动态选取代理)和 system(使用系统代理)。关于这个 API 的详细说明可以参看 Chrome 的
文档,这里有一份 中文翻译。
通过上面的代码也只是设置了代理服务器的 IP 地址和端口而已,用户名和密码还没有设置,这和使用
命令行参数没什么区别。所以还需要下面的第二行代码:
chrome.webRequest.onAuthRequired.addListener(
function (details) {
return {
authCredentials: {
username: "username",
password: "password"
}
};
},
{ urls: ["<all_urls>"] },
[ 'blocking' ]
);
我们先看看下面这张图,了解下 Chrome 浏览器接受网络请求的整个流程,一个成功的请求会经历一系
列的事件(图片来源):
这些事件都是由 chrome.webRequest API 提供,其中的 onAuthRequired 最值得我们注意,它是用于代
理身份认证的关键。所有的事件都可以通过 addListener 方法注册一个回调函数作为监听器,当请求需要
身份认证时,回调函数返回代理的用户名和密码。除了回调方法,addListener 第二个参数用于指定该代
理适用于哪些 url,这里的 <all_urls> 是固定的特殊语法,表示所有的 url,第三个参数字符串 blocking
表示请求将被阻塞,回调函数将以同步的方式执行。这个 API 也可以参考 Chrome 的 官方文档,这里是
中文翻译。
综上,我们就可以写一个简单的代理插件了,甚至将插件做成动态生成的,然后 Selenium 动态的加载生
成的插件。
三、Selenium 如何过滤非必要请求?
Selenium 配合代理,你的爬虫几乎已经无所不能了。上面说过,Selenium 爬虫虽然好用,但有个最大的
特点是慢,有时候太慢了也不是办法。由于每次打开一个页面 Selenium 都要等待页面加载完成,包括页
面上的图片资源,JS 和 CSS 文件的加载,而且更头疼的是,如果页面上有一些墙外资源,比如来自 Go
ogle 或 Facebook 等站点的链接,如果不使用境外代理,浏览器要一直等到这些资源连接超时才算页面
加载完成,而这些资源对我们的爬虫没有任何用处。
|
|