您好,欢迎访问代理记账网站
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

11.抓取JavaScript

客户端脚本语言是运行在浏览器而非服务器上的语言。客户端语言成功的前提是浏览器能够正确地解释和执行这类语言。
通常在网上遇到的客户端语言只有两种:ActionScript(开发 Flash 应用的语言)和JavaScript。总之,抓取 Flash 页面的需求并不多,现代网页中普遍使用的客户端语言是JavaScript。
JavaScript 是 Web 上最常用也是支持者最多的客户端脚本语言。它可以收集用户跟踪数据,不需要重载页面直接提交表单,在页面中嵌入多媒体文件,甚至运行在线游戏。网页源代码的script 标签之间就是JavaScript。
1.JavaScript简介
JavaScript 是一种弱类型语言,其语法通常可以与 C++ 和 Java 相比。
下面的 JavaScript 程序通过递归方式计算斐波纳契序列:

<script>
function fibonacci(a, b){
	var nextNum = a + b;
	console.log(nextNum+" is in the Fibonacci sequence");
	if(nextNum < 100){
		fibonacci(b, nextNum);
	}
}
fibonacci(1, 1);
</script>

在这里插入图片描述
JavaScript 里所有的变量都用 var 关键字进行定义。这与 PHP 里的 $ 符号,或者Java 和 C++ 里的类型声明(int、String、List 等)类似。Python 不太一样,它没有这种显式的变量声明。
JavaScript 还有一个非常好的特性,就是把函数作为变量使用:

<script>
var fibonacci = function() {
	var a = 1;
	var b = 1;
	return function() {
		var temp = b;
		b = a + b;
		a = temp;
		return b;
	}
}
var fibInstance = fibonacci();
console.log(fibInstance()+" is in the Fibonacci sequence");
console.log(fibInstance()+" is in the Fibonacci sequence");
console.log(fibInstance()+" is in the Fibonacci sequence");
</script>

在这里插入图片描述
变量 fibonacci 被定义成一个函数。它的函数值返回一个函数,该函数会打印斐波纳契序列里不断增大的值。每次被调用时,它都会返回斐波纳契的计算函数,该函数再次执行序列计算,并增加函数变量的值。
在处理用户行为和回调函数时,把函数作为变量进行传递是非常方便的。
常见 JavaScript 库
在查看网页源代码的时候,你可能会看到一种或多种常用的 JavaScript 库。
(1)jQuery
jQuery 是一个十分常见的库,使用了 jQuery 的网站很好识别,其源代码里包含了 jQuery 的入口,比如:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></
script>

jQuery 可以动态地创建 HTML 内容,这些内容只有在 JavaScript 代码执行之后才会显示。如果你用传统的方法抓取页面内容,就只能获得 JavaScript 代码执行之前页面上的内容。
(2)Google Analytics
受欢迎的用户跟踪工具。很容易判断一个页面是不是使用了 Google Analytics。如果网站使用了它,在页面底部会有类似如下所示的 JavaScript 代码:

<!-- Google Analytics -->
<script type="text/javascript">

var _gap = _gap || [];
_gap.push(['_setAccount', 'UA-4591498-1']);
_gap.push(['_setDomainName', 'oreilly.com']);
_gap.push(['_addIgnoreRef',  'oreilly.com']);
_gap.push(['_setSiteSpeedSampleRate', 50]);
_gap.push(['_trackPageview']);

(function() { var ga = ducument.createElement('Script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })();
</script>

如果一个网站使用了 Google Analytics 或其他类似的网络分析系统,而你不想让网站知道你在抓取它的数据,就要确保把那些分析工具的 cookie 或者所有 cookie 都关掉。
(3)Google Maps
用 Google Maps 的 API 很容易在任何网站上嵌入带有自定义信息的地图。
理解 Google Maps 的工作方式可以让你轻松地获取格式规范的经纬度坐标和具体地址。在 Google Maps 上,显示一个位置最常用的一种方法就是用标 记(一个大头针)
可以用下面的代码将标记插在 Google Maps 上:

var marker = new google.maps.Marker({
	positon: new google.maps.LatLng(-25.363882, 131.044922),
	map: map,
	title: 'Some marker text'
});

Python 可以轻松地抽取出所有位置在 google.maps.LatLng() 里的坐标,生成一组经纬度坐标值。
通过 Google 的 Reverse Geocoding API,你可以把这些经纬度坐标组解析成格式规范的地址,便于存储和分析。

2.Ajax和动态HTMl
到目前为止,我们与 Web 服务器通信的唯一方式,就是发出 HTTP 请求获取新页面。如果提交表单之后,或者从 Web 服务器获取信息时,网站的页面不需要重新加载,那么你访问的网站很可能使用了 Ajax 技术。
Ajax 其实并不是一门语言,而是用来完成某个任务的一系列技术。Ajax 的全称是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML),网站不需要使用单独的页面请求就可以和 Web 服务器进行交互(收发信息)。某网页使用Ajax,可以说是“这个表单使用 Ajax 与 Web 服务器通信”。
与Ajax 一样,动态 HTML(dynamic HTML,DHTML)也是用于某一常见目的的一系列技术。DHTML 是客户端脚本改变页面的 HTML 元素时,改变的 HTML 代码、CSS 语言,或者二者兼而有之。区分页面是否是动态HTMl,关键要看有没有用 JavaScript 控制 HTML 和 CSS 元素。
当出现在浏览器上看到的内容,与用爬虫从网站上抓取的内容不一样。或者有时候网页用一个加载页面把你重定向到另一个结果页面,但是网页的 URL链接在这个过程中一直没有变化。那么是因为爬虫不能执行那些让页面产生各种神奇效果的 JavaScript 代码。而浏览器可以正确地执行 JavaScript。
对于那些使用了 Ajax 或 DHTML 技术来改变 / 加载内容的页面, Python 解决这个问题只有两种途径:直接从 JavaScript 代码里抓取内容,或者用Python 的第三方库执行 JavaScript,直接抓取你在浏览器里看到的页面。
(1)在Python中用Selenium执行JavaScript
Selenium 是一个强大的网页抓取工具,最初是为网站自动化测试而开发的。现广泛用于获取精确的网站快照,因为网站可以直接运行在浏览器中。Selenium 可以让浏览器自动加载网站,获取需要的数据,甚至对页面截屏,或者判断网站上是否发生了某些操作。
Selenium 自己不带浏览器,它需要与第三方浏览器集成才能运行。如果喜欢让程序在后台静静地运行,可以用一个叫 PhantomJS 的工具代替真实的浏览器。
PhantomJS 是一个无头浏览器(headless browser)。它会把网站加载到内存并执行页面上的JavaScript,但是它不会向用户展示网页的图形界面。把 Selenium 和 PhantomJS 结合在一起,就可以运行一个非常强大的网络爬虫来轻松处理 cookie、JavaScript、header 以及任何你需要做的事情。
Selenium 库可以从 PyPI 网站下载 ,也可以用第三方管理器(比如 pip)用命令行安装。 pip install selenium
PhantomJS 可以从它的官方网站下载。因为 PhantomJS 是一个功能完善(虽然无头)的浏览器,并非一个 Python 库,所以需要下载并安装才能使用。
书中测试的网站是:http://pythonscraping.com/pages/javascript/ajaxDemo.html,页面显示简单文字,2秒后替换成由Ajax生成的内容。
Selenium 库是一个在 WebDriver 对象上调用的 API。WebDriver 有点儿像可以加载网站的浏览器,但是它也可以像 BeautifulSoup 对象一样用来查找页面元素,与页面上的元素交互(发送文本、点击等),以及执行其他动作来运行网络爬虫。
以下代码可获取测试网站2秒后Ajax“墙”上的内容:

from selenium import webdriver
import time

driver = webdriver.PhantomJS(executable_path='D:/Web_crawler/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.get('http://pythonscraping.com/pages/javascript/ajaxDemo.html')
time.sleep(3)
print(driver.find_element_by_id('content').text)
driver.close()

在这里插入图片描述
Selenium 的 WebDriver 接入点指明 PhantomJS 可执行文件的路径:executable_path。

Selenium的选择器
代码中用的是find_element_by_id,也可以使用以下选择器(如果是选择页面上多个元素,则用elements复数形式):
driver.find_element_by_css_selector(’#content’)
driver.find_element_by_tag_name(‘div’)
用BeautifulSoup来解析网页内容,可以用WebDriver的page_source函数返回页面的源代码字符串:
pageSource = driver.page_source
bs = BeautifulSoup(pageSource, ‘html.parser’)
print(bs.find(id=‘content’).get_text())

将time.sleep(3)改为time.sleep(1),运行结果为:
在这里插入图片描述
在处理其他规模较大的网站时可能会出问题,页面的加载时间是不确定的,具体依赖于服务器某一毫秒的负载情况,以及不断变化的网速。
一种更加高效的方法是,让 Selenium 不断地检查某个元素是否存在,以此确定页面是否已经完全加载,如果页面加载成功就执行后面的程序。
下面的程序检查 ID 为 loadedButton 的按钮是否存在,以此判断页面是不是已经完全加载:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.PhantomJS(executable_path='D:/Web_crawler/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.get('http://pythonscraping.com/pages/javascript/ajaxDemo.html')
try:
	element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'loadedButton')))
finally:
	print(driver.find_element_by_id('content').text)
	driver.close()

在这里插入图片描述
代码中导入的新模块WebDriverWait 和 expected_conditions,这两个模块组合起来构成了 Selenium 的隐式等待(implicit wait)。
隐式等待是等 DOM 中某个状态发生后再继续运行代码,而显式等待明确设置了等待时间。在隐式等待中,DOM 触发的状态是用 expected_conditions 定义的,在Selenium 库里面元素被触发的期望条件(expected condition)有很多种,包括:

  • 弹出一个提示框
  • 一个元素(比如文本框)被选中
  • 页面的标题改变了,或者文本显示在页面上或者某个元素里
  • 一个元素对 DOM 可见,或者一个元素从 DOM 中消失了

大多数期望条件在使用前都需要你先指定等待的目标元素。元素用定位器(locator)指定。定位器与选择器是不一样的,定位器是一种抽象的查询语言,用 By 对象表示,可以用于不同的场合,包括创建选择器。例如:

EC.presence_of_element_located((By.ID, 'loadedButton'))

定位器还可以用来创建选择器,配合WebDriver的find_element函数使用:(与上述代码中的打印语句功能相同)

print(driver.find_element(By.ID, 'content').text)

如果你可以不用定位器,就不要用,毕竟这样可以少导入一个模块。但是定位器是一种十分方便的工具,可以用在不同的应用中,并且具有很强的灵活性。
下面是定位器通过By对象进行选择的策略:

  • ID,通过HTMl的id属性查找元素
  • CLASS_NAME,通过HTMl的class属性来查找元素。
  • CSS_SELECTOR,通过CSS的class、id、tag属性名来查找元素,用#idName、.className、tagName表示。
  • LINK_TEXT,通过链接文字查找HTML的a标签。例如一个链接文字是“Next”,则用 (By.LINK_TEXT, “Next”) 来选择。
  • PARTIAL_LINK_TEXT,与LINK_NEXT类似,只是通过部分链接文字来查找。
  • NAME,通过name属性查找HTML标签。
  • TAG_NAME,通过标签的名称查找HTML标签。
  • XPATH,用 XPath 表达式选择匹配的元素。

XPath 语法
XPath(XML Path,XML 路径)是在 XML 文档中导航和选择元素的查询语言。
XPath 语法中有 4 个重要概念。
1.根节点和非根节点
— /div 选择 div 节点,只有当它是文档的根节点时
— //div 选择文档中所有的 div 节点(包括非根节点)
2.通过属性选择节点
— //@href 选择带 href 属性的所有节点
— //a[@href=‘http://google.com’] 选择文档中所有指向 Google 网站的链接
3.通过位置选择节点
— //a[3] 选择文档中的第 3 个链接
— //table[last()] 选择文档中的最后一个表
— //a[position() < 3] 选择文档中的前 3 个链接
4.星号()匹配任意字符或节点,可以在不同情况下使用
— //table/tr/
选择所有表格中 tr 标签的所有子节点(这很适合选择 th 和 td 标签)
— //div[@*] 选择带任意属性的所有 div 标签
·
XPath 还有很多高级的语法特征。可以使用布尔逻辑、函数(如 position()),以及大量这里没介绍的操作符。

(2)Selenium的其他webdriver
上面Selenium 使用的是 PhantomJS driver,浏览器不会弹出就可以直接开始抓取网站。但是有时候使用另外一种类型的浏览器来运行你的爬虫可能很有用。基于以下几点:

  • 故障排除。如果使用其他的浏览器,你可以在任意断点暂停代码的运行,并与网页进行交互。
  • 测试可能只能采用特定的浏览器来运行。
  • 非常注重细节的网站在不同浏览器上的表现可能不同。
    Selenium小组创建了一个webdriver集合(http://www.seleniumhq.org/download/)可供参考:
firefox_driver = webdriver.Firefox('<path to Firefox webdriver>')
chrome_driver = webdriver.Chrome('<path to Chrome webdriver>')
safari_driver = webdriver.Safari('<path to Safari webdriver>')
ie_driver = webdriver.Ie('<path to Internet Explorer webdriver>')

3.处理重定向
客户端重定向是在服务器将页面内容发送到浏览器之前,由浏览器执行 JavaScript 完成的页面跳转,而不是服务器完成的跳转。
根据处理方式,服务器端重定向可以轻松地通过 Python 的 urllib 库解决,而不需要使用 Selenium。客户端重定向却不能这样处理,除非你有工具可以执行 JavaScript。
Selenium 可以执行这种 JavaScript 重定向,就像执行其他 JavaScript 一样;但是这类重定向的主要问题是什么时候停止页面执行,可以用一种智能的方法来检测客户端重定向是否已完成。
首先从页面开始加载时就“监视”DOM 中的一个元素,然后重复调用这个元素,直到 Selenium 抛出一个StaleElementReferenceException 异常;也就是说,元素不在页面的 DOM 里了,这说明此时网站已经跳转。

from selenium import webdriver
import time
from selenium.webdriver.remote.webelement import WebElement
from selenium.common.exceptions import StaleElementReferenceException

def waitForLoad(driver):
	elem = driver.find_element_by_tag_name('html')
	count = 0
	while True:
		count +=1
		if count > 20:
			print('Timing out after 10 seconds and returning')
			return
		time.sleep(.5)
		try:
			elem == driver.find_element_by_tag_name('html')
		except StaleElementReferenceException:
			return

driver = webdriver.PhantomJS(executable_path='D:/Web_crawler/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.get('http://pythonscraping.com/pages/javascript/redirectDemo1.html')
waitForLoad(driver)
print(driver.page_source)

在这里插入图片描述
重定向后网址:http://pythonscraping.com/pages/javascript/redirectDemo2.html
这个程序每半秒钟检查一次网页,看看 html 标签还在不在,时限为 10 秒钟。检查的时间间隔和时限都可以按需随意调整。另外也可以编写一个类似的循环来检查当前页面的 URL,直到 URL 发生改变,或者匹配到你寻找的特定的 URL。
等待元素的出现和消失是 Selenium 中一个常见的任务,这里提供一个 15 秒钟的时限和一个 XPath 选择器,该 XPath 选择器寻找页面内容以完成同样的任务。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

driver = webdriver.PhantomJS(executable_path='D:/Web_crawler/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.get('http://pythonscraping.com/pages/javascript/redirectDemo1.html')
try:
	bodyElement = WebDriverWait(driver, 15).until(EC.presence_of_element_located((By.XPATH, '//body[contains(text(), "This is the page you are looking for!")]')))
	print(bodyElement.text)
except TimeoutException:
	print('Did not find the element')

在这里插入图片描述
4.关于JavaScript的最后提醒
在大多数情况下,JavaScript 的使用并不影响你抓取网页的方式。JavaScript 可能仅用来控制网站的跟踪工具、控制网站的一小部分,或者操作一个下拉菜单。如果 JavaScript 确实影响你抓取网站的方式,也很容易通过 Selenium 这样的工具来执行它,以生成简单的 HTML 页面。
JavaScript 的目标是生成 HTML 和 CSS,然后被浏览器渲染,或者是通过 HTTP 请求和响应与服务器动态通信。一旦使用了 Selenium,页面上的 HTML 和 CSS 就可以和其他网站代码一样被读取和解析,HTTP 请求和响应也可以在不使用 Selenium 的情况下用前几章中介绍的技术来发送和处理。
JavaScript 对于网络爬虫来说甚至会带来一些好处,因为它作为“浏览器端的内容管理系统”,可能会向外界暴露出有用的 API,让你可以更直接地获取数据。
若仍然难以应对某种复杂的 JavaScript,则查看15章关于 Selenium 以及直接与动态网站交互(包括拖放动作)的信息。


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进