scrapy介绍
Scrapy是一个健壮的抓取网络资源的框架。该框架可以将网上的资源保存到Excel中,也可以将不同的资源整合起来。Scrapy可以帮你完成简单和复杂的数据提取。
使用Scarpy,只需进行一项设置,就可以我完成大量工作。它可以让我们进行串联操作,清洗、形成、丰富数据,或者存入数据库等等,同时不会有太大的消耗。Scrapy也可以读懂破损的HTML。目前Scrapy已经出现了五年多,已经基本成熟稳定。
理解HTML和XPath
为了从网页提取信息,了解网页的结构是非常必要的。
HTML、DOM树结构和XPath
从本书的角度,输入网址到看到网页的整个过程可以分为四步:
- 浏览器中输入网址URL。URL的第一部分,也叫域名,用来搜寻网络上的服务器。URL的其他cookies等数据形成一个发送到服务器的请求request。
- 服务器向浏览器发送HTML。也可能是XML或者JSON等其他格式的数据。
- HTML在浏览器内部转化为树结构:文档对象模型(DOM)
- 根据布局规范,树结构转化为屏幕上的真实页面。
HTML文档
服务器读取URL,回复一个HTML文档。HTML在本质上是一个文本文件,不过它严格遵循万维网联盟的规定格式。例如:
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type"
content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,
initial-scale=1" />
<style type="text/css"> body { background-color: ...
} </style>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is established to be used for
illustrative examples examples in documents.
You may use this domain in examples
without prior coordination or asking for
permission.</p>
<p><a href="http://www.iana.org/domains/example">
More information...</a></p>
</div>
</body>
</html>
这是http://example.com的网页源代码。在HTML中,尖括号里的字符称作标签。某些网页没有结束标签,例如只用标签分隔段落,这种行为是容许的,会自主判断哪里该有结束标签。
XPth表达式
HTML文档的层级结构的最高级是标签,可以使用元素名和斜杠线选择任意元素。例如,下面的表达式返回了http://example.com/上对应的内容:
$x('/html')
[ <html>...</html> ]
$x('/html/body')
[ <body>...</body> ]
$x('/html/body/div')
[ <div>...</div> ]
$x('/html/body/div/h1')
[ <h1>Example Domain</h1> ]
$x('/html/body/div/p')
[ <p>...</p>, <p>...</p> ]
$x('/html/body/div/p[1]')
[ <p>...</p> ]
$x('/html/body/div/p[2]')
[ <p>...</p> ]
标签在标签内有两个,所以会返回两个。可以使用p[1]和p[2]分别返回两个元素。
对于大文档,我们可能会写很长的XPath来获取内容。为了避免这点,两个斜杠线//来访问所有的同名的元素。例如,//p可以选择所有的p元素,//a可以选择所有的链接。例如:
$x('//p')
[ <p>...</p>, <p>...</p> ]
$x('//a')
[ <a href="http://www.iana.org/domains/example">More information...</a> ]
可以选择标签,也可以选择属性。选择网页的上链接,可以通过下面的方式找到:
$x('//a/@href')
[href="http://www.iana.org/domains/example"]
也可以通过text()函数选择文字:
$x('//a/text()')
["More information..."]
可以使用“ * ”标志选择某层下所有的元素:
$x('//div/*')
[<h1>Example Domain</h1>, <p>...</p>, <p>...</p>]
在http://www.w3schools.com/xsl/xsl_functions.asp在线文档中你可以扎到更多的类似的函数。
常见工作
下面展示一些XPath表达式的常见使用。先来看看在维基百科上是怎么使用的。例如:
- 取得id为firstHeading的div下的span的text:
//h1[@id="firstHeading"]/span/text()
- 取得id为toc的div下的ul内的URL:
//div[@id="toc"]/ul//a/@href
- 在任意class包含ltr和class包含skin-vector的元素之内,取得h1的text,这两个字符串可能在同一class内,或不在。
//*[contains(@class,"ltr") and contains(@class,"skin-vector")]//h1//text()
在实际应用中,会频繁的使用class。由于CSS的版式原因,可以看到HTML的元素总会包含许多特定的class属性。在XPath中,contains()函数可以帮助选择包含某一class的所有元素。例如:
- 选择class属性是infobox的第一张图片的URL:
//table[@class="infobox"]//img[1]/@src
- 选择class属性是reflist开头的div下面的所有URL链接:
//div[starts-with(@class,"reflist")]//a/@href
- 选择div下面的所有URL链接,并且这个div的下一个相邻元素的子元素包含文字References:
//*[text()="References"]/../following-sibling::div//a
- 取得所有图片的URL:
//img/@src
提前应对网页发生改变
爬取的目标常常位于远程服务器。这意味着,如果它的HTML发生了改变,XPath表达式就无效了,我们就不得不回过头修改爬虫的程序。因为网页的改变一般就很少,爬虫的改动往往不会很大。然而,我们还是宁肯不要回头修改。一些基本原则可以帮助我们降低表达式失效的概率:
- 避免使用数组组号 Chrome常常会在表达式中加入许多常数
//*[@id="myid"]/div/div/div[1]/div[2]/div/div[1]/div[1]/a/img
如果HTML上有一个广告窗的话,就会改变文档的结构,这个表达式就会失效。解决的方法是,尽量找到离img标签近的元素,根据该元素的id或class属性,进行抓取,例如:
//div[@class="thumbnail"]/a/img
- 用class抓取效果不一定好 使用属性可以方便的定位要抓取的元素,但是因为CSS也要通过class修改页面的外观,所以class属性可能会发生改变,例如下面用到的class:
//div[@class="thumbnail"]/a/img
过一段时间之后,可能会变成:
//div[@class="preview green"]/a/img```
-
数组指向的class优于排版指向的class
在上一个例子中,使用thumbnail和gree两个class都不好,但这两个都不如departure-time。前面两个是用来排版的,departure-time是有语义,和div中的内容有关。所以,在排版发生改变的情况下,departure-time发生改变的可能性比较小。应该说,网站作者在开发中十分清楚,为内容设置有意义、一致的标记,可以让开发过程收益。 -
id通常是最可靠的
只要id具有语义并且数据相关,id通常是抓取时最好的选择。部分原因是,JavaScript和外链锚点总是id获取文档中特定的部分。