在上一篇中 , 我们构建了一个爬虫, 可以通过跟踪链接的方式下载我们所
需的网页。 但是爬虫在下载网页之后又将 结果丢弃掉了 。 现在, 我们需要让这个爬虫从每个网页中抽取一些数据,然后实现某些事情, 这种做法也被称为抓取(scraping) 。
1.分析网页
在抓取之前我们首先应该了解网页的结构如何,可以用浏览器打开你要抓取的网页然后有点单机选择查看页面源代码,
推荐使用火狐浏览器 可以使用firebug工具进行查看 安装firebug http://jingyan.baidu.com/article/fdffd1f832b032f3e98ca1b2.html
可以右键单机我们在抓取中感兴趣的网页部分如图所示
2,三种网页的抓取方法
- 正则表达式
- BeautifulSoup模块
- lxml
2.1正则表达式
import requests
import re
def download(url, user_agent='jians', num_retries=2):
headers = {'User-agent': user_agent}
response = requests.get(url, headers=headers)
if num_retries > 0:
if 500 <= response.status_code < 600:
return download(url, num_retries - 1)
return response.text
def get_page():
url = 'http://example.webscraping.com/view/Unitled-Kingdom-239'
html = download(url)
# 通过尝试匹配<td>元素中的内容
html_str = re.findall('<td class="w2p_fw">(.*?)</td>', html)
print html_str
# 分离出面积熟悉,拿到第二个元素(area)的信息
area_str = re.findall('<td class="w2p_fw">(.*?)</td>', html)[1]
print area_str
# 通过指定tr的id去查询area,可以有效的防止网页发生变化
html_places = re.findall('<tr id="places_area__row">.*?'
'<td class="w2p_fw">(.*?)</td>', html)
print html_places
get_page()
正则表达式为我们提供了抓取数据的快捷方式, 但是该方法过于脆弱 , 容易在网页更新后出现问题
2.2 Beautiful Soup
Beautiful Soup 是一个非常流行的 Python 模块。 该模块可以解析网页, 并
提供定位 内 容的便捷接 口
安装beautifulsoup
pip install beautifulsoup4
beanuifulsoup4文档
https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
简单使用:
from bs4 import BeautifulSoup #导入beautifulsoup需要这样导入
import requests
def get_area():
'''使用该方法抽取示例 国家面积数据的完整代码。'''
url = 'http://example.webscraping.com/view/Unitled-Kingdom-239'
html = requests.get(url).text
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
get_area()
2.3Lxml
Lxml 是基于 libxml2 这一 XML 解析库的 Python 封装。 该模块使用 C
语言编写 , 解析速度 比 Beautiful Soup 更快.
安装模块
pip install lxml
ping install cssselect #需要css选择器模块
简单使用:
import lxml.html
import requests
def get_area():
'''将有可能不合法的HTML 解析为统一格式'''
broken_html = '<ul class = country> <li>Area<li>Population</ul>'
tree = lxml.html.fromstring(broken_html)
fixed_html= lxml.html.tostring(tree, pretty_print=True)
print fixed_html
def get_area1():
'''使用CSS选择器抽取面积数据的代码'''
url = 'http://example.webscraping.com/view/Unitled-Kingdom-239'
html = requests.get(url).text
tree = lxml.html.fromstring(html)
td = tree.cssselect('tr#places_area__row > td.w2p_fw')[0]
area = td.text_content()
print area
get_area()
get_area1()
2.4三种抓取方法的对比
用三种方式分别抓取1000次http://example.webscraping.com/view/Unitled-Kingdom-239 网页中的国家数据,并打印时间
代码:
#!/usr/bin/env python
# -*-coding:utf-8 -*-
import re
from bs4 import BeautifulSoup
import lxml.html
from lxml.cssselect import CSSSelector
import requests
import time
FIELDS = ('area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code',
'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours' )
def download(url, user_agent='jians', num_retries=2):
headers = {'User-agent': user_agent}
response = requests.get(url, headers=headers)
if num_retries > 0:
if 500 <= response.status_code < 600:
return download(url, num_retries - 1)
return response.text
def re_scarper(html):
results = {}
for field in FIELDS:
results[field] = re.search('<tr id="places_{}__row">.*?'
'<td class="w2p_fw">(.*?)</td>'
.format(field), html).groups()[0]
return results
def bs_scraper(html):
soup = BeautifulSoup(html, 'html.parser')
results = {}
for field in FIELDS:
results[field] = soup.find('table').find('tr', id='places_%s__row' % field).find('td', class_='w2p_fw').text
return results
def lx_scraper(html):
tree = lxml.html.fromstring(html)
results = {}
for field in FIELDS:
results[field] = tree.cssselect('table > tr#places_%s__row > td.w2p_fw' % field)[0].text_content()
return results
def get_counter():
NUM_ITERATIONS = 1000
html = download('http://example.webscraping.com/view/Unitled-Kingdom-239')
for name, scrapter in [('REGULAR expression', re_scarper),
('beautifulsoup', bs_scraper),
('lxml', lx_scraper)]:
start = time.time()
for i in range(NUM_ITERATIONS):
if scrapter == re_scarper:
re.purge()
result = scrapter(html)
assert(result['area'] == '244,820 square kilometres')
end = time.time()
print '%s:%.2f seconds' % (name, end-start)
注意代码格式,方法之间空两行
种方法之间 的相对差异应当是相 当 的 。 从结果中可以看出 , 在抓取我们的示
例 网页时, Beautiful Soup 比其他两种方法慢了超过 6 倍之多 。 实际上这一结
果是符合预期的 , 因 为 lxml 和正则表达式模块都是 C 语言编写 的 , 而
BeautifulSoup 则是纯 Python 编写的 。 一个有趣的事实是, lxml 表现得
和正则表达式差不多好。 由于 lxml 在搜索元素之前, 必须将输入解析为 内
部格式, 因此会产生额外的开销 。 而当抓取同一网页的多个特征时, 这种初
始化解析产生的开销就会降低, lxml 也就更具竞争力 。
结论
法 ( 如 Beautiful Soup) 也不成问题。 如果只需抓取少量数据 , 并且想要避免
额外依赖的话, 那么正则表达式可能更加适合。 不过, 通常情况下, lxml 是
抓取数据 的最好选择 , 这是 因 为 该方法既快速又健壮 , 而正则表达式和
Beautiful Soup 只在某些特定场景下有用。
为链接爬虫添加抓取回调
前面我们已经了解了 如何抓取国家数据,接下来我们需要将其集成到上
一篇的链接爬虫当中
获取该版本链接爬虫的完整代码, 可以访问
https : //bitbucket .org/wswp/code/src/tip/chapter02 /link crawler . py。
对传入的 scrape callback 函数定制化处理, 就能使
用该爬虫抓取其他网站了
import re
import lxml.html
from link_craw import link_crawler
FIELDS = ('area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours')
def scrape_callback(url, html):
'''使用lxml抓取'''
if re.search('/view/', url):
tree = lxml.html.fromstring(html)
row = [tree.cssselect('table > tr#places_{}__row > td.w2p_fw'.format(field))[0].text_content() for field in FIELDS]
print url, row
if __name__ == '__main__':
link_crawler('http://example.webscraping.com/', '/(index|view)', scrape_callback=scrape_callback)
在抓取网站时, 我们更希望能够复用这些数据 , 因此下面我们
对其功能进行扩展, 把得到的结果数据保存到 csv 表格中 , 其代码如下所示。
import csv
import re
import lxml.html
from link_craw import link_crawler
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__':
link_crawler('http://example.webscraping.com/', '/(index|view)', scrape_callback=ScrapeCallback())
程序就会将结果写入一个 csv 文件中 , 我们可以使用类似 Excel 或者 LibreOffice 的应用查看该文件,
我们完成了第一个可以工作的数据抓取爬虫!