最近需要在一个网站下载一批数据。但是输入一个查询,返回三四万条结果,每次只能导出500条,而且每次还得输入下载条目的范围!这样点击下载,还不要了我的老命。于是乎想自动化这个过程。
我的需求主要是两点:1. 要求自动化程度高。最好有直接模拟浏览器鼠标和键盘动作的成熟接口,比如在文本框输入,选择下拉列表,单选框,复选框,点击按钮等。2. 不要求效率。因为我要的数据量相对来说很小。3. python下的框架。因为平时几乎主要用python。
我不太懂网站技术,和网站沾边的经验只有两个:开发过一个很简单安卓的客户端,用python的scrapy框架写过爬虫来自动爬取新闻。所以了解一些客户端和服务端基本的交互方式、了解如何分析网页源代码、了解xpath语法。
刚开始针对这个问题,我连搜啥都不太清楚。知乎的这篇文章提供了很多有用信息:“Python 爬虫如何获取 JS 生成的 URL 和网页内容?” 顺着它我又权衡了很多方法,最后选择了Selenium。主要优点是学习成本极小,代码实现快。缺点是爬取效率低。想要高效率的朋友,就要花一些时间学习更复杂的工具包了。
网站技术
想要自动爬取网页,得了解一些基本的知识,这样做起来更快。这里简单介绍一下相关知识。
1. Request/response
request是客户端向服务端发起请求。输入一个网址对应一个request动作,这是最直观的。爬取静态网页的内容,只要知道网址就可以了。但是现在的网页很多都是动态的,鼠标指向或者点击网页中某些元素也会触发request动作,从而使网页动态更新部分内容,这部分内容是不能直接从静态网页中获取的。这种技术叫AJAX,不过我不太懂。这里的问题是我们可能根本不知道网址是什么,因此需要一些高级的接口,能处理动态内容。
response是服务端给客户端的返回内容。想要获取静态网页内容的话,直接从requeson里取就好了。
2. 分析网页源码
我们想要爬取网页上的某一部分信息,需要知道如何能定位到它。这里需要HTML,XPATH的知识。不知道的可以上w3school 在线教程:http://www.w3school.com.cn
查看网页源代码,鼠标指针指向网页任意地方,或者指向目标元素。右键鼠标,在下拉列表选择“检查元素”即可。如下是我右键“百度一下”所显示的网页源代码,是HTML格式的,我们可以看到对应的HTML代码。把它提取出来,我们可能需要div//@[class="head_wrapper"]//input[@type="submit"]的语句,这是XPATH语法,很好掌握。知道如何分析网页,我们又进了一步。
3. 网页基本元素操作
前进、后退、刷新、打开新选项卡、输入网址等;
文本框输入、选择下拉列表、单选框、复选框、点击按钮等。
我这里需要模拟的操作也就这么多了,对应的selenium接口可以参考 http://www.cnblogs.com/Ming8006/p/5727542.html。
4. Selenium介绍
一句话:Selenium是一个web应用的自动化测试工具集。
好多句话:Selenium 诞生于 2004 年,当在 ThoughtWorks 工作的 Jason Huggins 在测试一个内部应用时。作为一个聪明的家伙,他意识到相对于每次改动都需要手工进行测试,他的时间应该用得更有价值。他开发了一个可以驱动页面进行交互的 Javascript 库,能让多浏览器自动返回测试结果。那个库最终变成了 Selenium 的核心,它是 Selenium RC(远程控制)和 Selenium IDE 所有功能的基础。
实战练习
1.分析数据获取的过程
我的数据获取过程如下:
在A页面输入查询语句,点击submit;浏览器自动新开一个页面,跳转到新页面B,在文本框输入下载条目的范围;点击Export弹出弹窗,然后在下拉列表、单选框、复选框做一些选择,点击下载。然后浏览器就开始下载文件了。
网页A
网页B
2. 爬取过程
A. 安装Selenium
Selenium支持多种浏览器,我选用google chrome。下载地址:https://sites.google.com/a/chromium.org/chromedriver/。同时,当然要在python中安装selenium。 命令行输入pip install senenium 即可安装。
B. 配置环境变量
这一步需要将chromedriver的保存路径配置到操作系统的环境变量中,好让selenium能找到chromedriver。windows下配置环境变量PATH,linux或者mac可以选择配置到 .bash_rc中。配置方法很多,自行百度。
我用的是mac,不知为什么配置了不起作用!后来发现只有在代码里设置才能起作用。
C. 核心代码(python)
# 设置下载路径,将路径配置到ChromeOptions。
chromeptions = webdriver.ChromeOptions()
prefs = {'profile.default_content_settings.popups':0,'download.default_directory': query_dir}
chromeptions.add_experimental_option('prefs', prefs)
# 设置环境变量,启动浏览器。
chromedriver = CHROMEDRIVER_DIR # 设置成你自己的路径
os.environ["webdriver.chrome.driver"] = chromedriver
driver = webdriver.Chrome(executable_path=chromedriver,chrome_options=chromeptions)
# 设置隐形等待时间,因为点击后网站一段时间后才能返回内容,如果不等待会报超时异常。
driver.implicitly_wait(IMPLICIT_WAIT_TIME)
# 请求网页A
driver.get("http://demo.ovid.com/demo/ovidsptools/launcher.htm")
# 在网页A的两个文本框输入,并提交。
driver.find_element_by_name('D').clear()
driver.find_element_by_name('D').send_keys('mesz')
driver.find_element_by_name('SEARCH').clear()
driver.find_element_by_name('SEARCH').send_keys(str_search_query)
driver.find_element_by_name('ovid').click()
# 跳转到新窗口,并将焦点定位到该窗口。
current_window_handle = driver.current_window_handle
for hdl in driver.window_handles: # selenium总是有两个handle
if hdl != current_window_handle:
new_window_handle = hdl
driver.switch_to.window(new_window_handle)
driver.implicitly_wait(IMPLICIT_WAIT_TIME)
# 获取到网页。首先获取返回的总条目数,然后提取文本框输入下载条目的范围,如1-500。然后点击Export。
# 注意:等待页面加载完成后再计算下载次数
search_ret_num = WebDriverWait(driver, EXPLICIT_WAIT_TIME, EXPLICIT_WAIT_INTERVAL).until(EC.presence_of_element_located((By.XPATH,'//*[@id="searchaid-numbers"]')))
search_ret_num =int(re.findall(r'\d+', search_ret_num.text.encode('utf-8'))[0])
list_range = chunks_by_element(range(1, search_ret_num+1), DOWNLOAD_NUM_PER_TIME)
for item in list_range:
download_range = driver.find_element_by_xpath('//*[@id="titles-display"]//input[@title="Range"]')
download_range.clear()
download_range.send_keys('{}-{}'.format(item[0], item[-1]))
# 点击 Export
export = driver.find_element_by_xpath('//*[@id="titles-display"]//input[@value="Export"]')
export.click()
# 获取到弹窗。进行一些设置。
driver.switch_to.alert
WebDriverWait(driver, EXPLICIT_WAIT_TIME, EXPLICIT_WAIT_INTERVAL).until(EC.presence_of_element_located((By.XPATH,'//div[@id="export-citation-popup"]')))
# 设置下载文件的一些配置
export_to_options = driver.find_element_by_xpath('//select[@id="export-citation-export-to-options"]')
export_to_options.find_element_by_xpath('//option[@value="xml"]').click()# XML
# 设置 citation content radio
citation_options = driver.find_element_by_xpath('//ul[@id="export-citation-options"]')
citation_options.find_element_by_xpath('//input[@value="ALL"]').click()# Complete Reference
# 设置 include check-box
citation_include = driver.find_element_by_xpath('//div[@id="export-citation-include"]')
ifcitation_include.find_element_by_xpath('//input[@name="externalResolverLink"]').is_selected():# Link to External Resolver
citation_include.find_element_by_xpath('//input[@name="externalResolverLink"]').click()
ifcitation_include.find_element_by_xpath('//input[@name="jumpstartLink"]').is_selected():# Include URL
citation_include.find_element_by_xpath('//input[@name="jumpstartLink"]').click()
ifcitation_include.find_element_by_xpath('//input[@name="saveStrategy"]').is_selected():# Search History
citation_include.find_element_by_xpath('//input[@name="saveStrategy"]').click()
# 点击下载。
download = driver.find_element_by_xpath('//div[@class ="export-citation-buttons"]')
download.click()
finally:
sleep(30)# wait for finishing downloading the last file
# driver.implicitly_wait(30) # doesn't work!
driver.quit()
return
3. 小贴士
A. 每次启动一个浏览器,桌面就会真的弹出一个浏览器。你可以清晰地看到自动化过程是如何的。看来selenium真的就是为web程序的自动化测试准备的。另外,爬取过程中要注意屏幕保持打开。如果进入休眠或者屏保,也会抛出异常的。
B. 模拟网页操作的时候,网页跳转是很常见的场景。因此要注意网页响应时间。selenium不会等待网页响应完成再继续执行代码,它会直接执行。二者应该是不同的进程。这里可以选择设置隐性等待和显性等待。在其他操作中,隐性等待起决定性作用,在WebDriverWait..中显性等待起主要作用,但要注意的是,最长的等待时间取决于两者之间的大者,如果隐性等待时间 > 显性等待时间,则该句代码的最长等待时间等于隐性等待时间。
C. 设置下载路径时,刚开始怎么都不起作用。我怀疑是key “download.default_directory”不对,于是通过查看网页源代码,找到了key,依然一样的。问题出在其他地方。不过这里提醒了我,以后在代码中用字典做相关的配置时,可以通过查看源代码的方式来猜测。
D. 原以为实现整个过程最起码的两三天,因为我真的不懂。从开始学习到做完不到一个白天就完成了。估计是因为我动手之前搜了很长时间,反复比对之后,找了个最得心应手的工具。
E. 完成后我在github上搜了一圈,发现了一个神器https://github.com/voliveirajr/seleniumcrawler。 对于想爬取大量内容的朋友,如果还不想浪费时间学习太多web应用底层的知识,可以结合使用Selenium+scrapy。scrapy可以负责搜索网页,selenium负责处理每个网页上的内容,尤其是动态内容。下次我如果有需求,打算用这个思路了!
F. 分享一句话。“关于爬虫,涨经验最快的方式是:学学怎么写网站,你知道网站是什么发请求的,就知道怎么爬网站了!” 很简单吧,不过这么简单的一句话给我很大的启发。之前就是感觉太难,一直停留在scrapy爬取静态网页的水平。而且像cookies之类的技术也看了,看一次忘一次。现在看来,还是因为没有把网站的整体流程梳理清楚。另一方面,也是畏惧那些繁杂的网站技术名词。其实只要上网查查相关的概念怎么回事,慢慢就打通了。
G. 最后,完全不懂编程的人可以用一些可视化的爬虫工具,这里有一些介绍:https://www.sdk.cn/news/4801。懂编程的且想要高效率的就需要参考其他工具了。