WSWP(用python写爬虫)笔记二:实现链接获取和数据存储爬虫

前面已经构建了一个获取链接的爬虫模块,现在继续完善这个爬虫。

分析网页

要对一个网页中的数据进行提取,需要先对网页的结构有一个大体的了解,通过在浏览器查看网页源代码的方法就能实现。
在了解到网页的结构后,接下来便是获取自己想要的数据了,个人比较喜欢用Chrome浏览器的检查元素的方式来定位数据在html源码中的位置(根据个人喜好来选择,不过建议直接使用chrome自带的,后面获取解析内容的css selector或XPath比较方便)。例如,我想要爬取某篇blog的内容,直接在Chrome中打开blog的url,并在感兴趣的内容上依次点击右键 -> 检查便能定位到数据在网页源码中的位置。如图所示:
oops
结果如下:
oops

如此便得到了想要爬取的内容存在于标签< div class=”markdown_views”>之中。
接下来便是通过解析网页获取到目标内容并进行抓取了。

数据抓取

测试url: http://example.webscraping.com/places/default/view/China-47
抓取的数据: 国土面积
oops
数据在网页源码中的位置:
oops
可以看出所想抓取的数据的位置在< 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)

结果如下:
oops
从上面的结果中可以看出,解析出了很多内容,返回到源码中可以看出多个属性都使用了< 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)

结果如下:
oops

上一个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())

结果如下:
oops

上面的callback,使用了回调类,而不再是回调函数,以便保持csv中writer的属性状态。csv的writer属性在构造方法中进行了实例化处理,然后在 __call__ 方法中执行多次写操作。__call__()是一个特殊的方法,在对象作为函数被调用是会调用该方法。

成功了,完成了一个可以工作的数据获取爬虫了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值