前面已经构建了一个获取链接的爬虫模块,现在继续完善这个爬虫。
分析网页
要对一个网页中的数据进行提取,需要先对网页的结构有一个大体的了解,通过在浏览器查看网页源代码的方法就能实现。
在了解到网页的结构后,接下来便是获取自己想要的数据了,个人比较喜欢用Chrome浏览器的检查元素的方式来定位数据在html源码中的位置(根据个人喜好来选择,不过建议直接使用chrome自带的,后面获取解析内容的css selector或XPath比较方便)。例如,我想要爬取某篇blog的内容,直接在Chrome中打开blog的url,并在感兴趣的内容上依次点击右键 -> 检查便能定位到数据在网页源码中的位置。如图所示:
结果如下:
如此便得到了想要爬取的内容存在于标签< div class=”markdown_views”>之中。
接下来便是通过解析网页获取到目标内容并进行抓取了。
数据抓取
测试url: http://example.webscraping.com/places/default/view/China-47
抓取的数据: 国土面积
数据在网页源码中的位置:
可以看出所想抓取的数据的位置在< td class=”w2p_fw”>中。
接下来介绍三种抓取数据的方式,首先是经典的正则表达式匹配法,然后是python中比较流行的Beautifulsoup模块,最后是强大的lxml模块。
正则表达式
对于正则表达式不清楚的,可以先阅读 正则表达式。
通过正则表达式抓取面积数据时,首先尝试匹配< td class=”w2p_fw”>中的内容,示例如下:
# re_test.py
from wswp_1_pro.downloader import download
import re
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
for content in re.findall('<td class="w2p_fw">(.*?)</td>', str(html)):
print(content)
结果如下:
从上面的结果中可以看出,解析出了很多内容,返回到源码中可以看出多个属性都使用了< td class=”w2p_fw”>标签。如果想要分离出面积属性,可以只选择其中的第二个元素即可。
# re_test.py
from wswp_1_pro.downloader import download
import re
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
# for content in re.findall('<td class="w2p_fw">(.*?)</td>', str(html)):
# print(content)
area = re.findall('<td class="w2p_fw">(.*?)</td>', str(html))[1]
print("面积为:", area)
结果如下:
使用正则表达式的缺点是不够灵活,如果网页的将属性值更改以后,则需要重新更改正则表达式。
BeautifulSoup模块
BeautifulSoup在网页数据解析时非常强大,提供定位内容的便捷接口。
首先通过pip安全该模块。
pip install bs4
使用BeautifulSoup的第一步是将已经下载好的html内容解析为soup文档(对网页内容进行正确解析并对未闭合的标签进行闭合)。然后可以使用find()和findall()来定位所需要的元素了。
以下是使用BeautifuSoup提取示例国家面积数据的完整代码:
from bs4 import BeautifulSoup
from wswp_1_pro.downloader import download
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
soup = BeautifulSoup(html, "html.parser")
# 先定位面积的父标签
tr = soup.find(attrs={'id':'places_area__row'})
# 通过父标签再来定位子标签
td = tr.find(attrs={'class':'w2p_fw'})
area = td.text
print("面积为:", area)
lxml模块
lxml官网点击这里^_^。
lxml pdf文档下载。
lxml通过pip安装。
pip install lxml
lxml模块跟BeautifulSoup一样,将下载的html解析为统一格式(修复不闭合的标签)。示例代码如下:
from wswp_1_pro.downloader import download
import lxml.html
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
tree = lxml.html.fromstring(str(html))
# 从面积的父标签开始提取内容
td = tree.cssselect('tr#places_area__row > td.w2p_fw')[0]
area = td.text_content()
print("面积为:", area)
上述代码中使用cssselect(CSS选择器)可通过pip直接安装。
lxml有几种不同的方法进行元素的选择,比如XPath选择器、CSS选择器和类似于BeautifulSoup中的find方法。选用CSS选择器,因为其更加简洁,并且在能够解析动态内容。(可通过Chrome中选择检查元素,copy –> Copy selector来直接从浏览器中获取到CSS选择器的参数,然后再根据实际情况进行精简处理),CSS选择器的参考教程请点这里。
使用XPath选择器的代码如下:
from wswp_1_pro.downloader import download
import lxml.html
url = 'http://example.webscraping.com/places/default/view/China-47'
html = download(url, headers={'User-agent':'wswp'}, proxy=None, numRetries=2)
tree = lxml.html.fromstring(str(html))
# 从面积的父标签开始提取内容
td = tree.xpath('//tr[@id="places_area__row"]/td[@class="w2p_fw"]')[0]
area = td.text_content()
print("面积为:", area)
以上就是从网页中获取感兴趣的数据的方法,接下来就是向爬虫程序中添加针对爬取到的数据的处理方法了。
为链接爬虫添加抓取回调
前面实现的链接爬虫如果想要复用到抓取其他网站的数据,需要添加一个callback参数参与抓取行为。callback是一个函数,在发生某个特定事件之后会调用该函数(在本例中会在网页下载完成后调用)。
实现的callback函数包含url和html两个参数,并且可以反回一个待爬取的url列表。在linkCrawler.py中添加代码,如下所示:
# linkCrawler.py
def linkCrawler(..., scrapeCallBack=None):
...
links = []
if scrapeCallBack:
links.extend(scrapeCallBack(url, html) or [])
...
现在只需要对传入的callback函数进行定制化处理,就能使用该爬虫对其他网站进行爬取了。下面对使用lxml抓取国土面积的代码进行修改,使其能在callback中使用。
import re
import lxml.html
from wswp_1_pro.linkCrawler import linkCrawler
FIELDS = ['area', 'population', 'iso', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours']
def scrapeCallBack(url, html):
if re.search('/view/', url):
tree = lxml.html.fromstring(str(html))
row = [tree.cssselect('table > tr#places_%s__row > td.w2p_fw' % field)[0].text_content() for field in FIELDS]
for text, content in zip(FIELDS,row):
print(text + ' : ' + content)
if __name__ == "__main__":
linkCrawler('http://example.webscraping.com/', '.*?/(index|view)', maxDepth=2, scrapeCallBack=scrapeCallBack)
结果如下:
上一个callback函数会去抓取国家数据,然后显示出来。通常情况下,在抓取网站时,更希望能够复用这些数据,下面对callback的功能进行扩展,将得到的结果数据保存到CSV表格中,代码如下:
# callBack_test.py
import csv
import re
import lxml.html
from wswp_1_pro.linkCrawler import linkCrawler
class ScrapeCallback:
def __init__(self):
self.writer = csv.writer(open('countries.csv', 'w'))
self.fields = ('area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours')
self.writer.writerow(self.fields)
def __call__(self, url, html):
if re.search('/view/', url):
tree = lxml.html.fromstring(html)
row = []
for field in self.fields:
row.append(tree.cssselect('table > tr#places_{}__row > td.w2p_fw'.format(field))[0].text_content())
self.writer.writerow(row)
if __name__ == '__main__':
linkCrawler('http://example.webscraping.com/', '/(index|view)', maxDepth=3, scrapeCallBack=ScrapeCallback())
结果如下:
上面的callback,使用了回调类,而不再是回调函数,以便保持csv中writer的属性状态。csv的writer属性在构造方法中进行了实例化处理,然后在 __call__ 方法中执行多次写操作。__call__()是一个特殊的方法,在对象作为函数被调用是会调用该方法。
成功了,完成了一个可以工作的数据获取爬虫了。