1.4 爬虫修炼之道——从网页中提取结构化数据并保存(以爬取糗百文本板块所有糗事为例)

欢迎大家关注我的专题:爬虫修炼之道

上篇 爬虫修炼之道——编写一个爬取多页面的网络爬虫主要讲解了如何使用python编写一个可以下载多页面的爬虫,如何将相对URL转为绝对URL,如何限速,如何设置代理。本篇将讲解如何从下载下来的html文件中提取结构化数据。涉及到的模块有:

  • re - python中和正则表达式相关的库
  • urlparse 此模块定义了一个标准接口,用于组合URL字符串,并将“相对URL”转换为给定“基本URL”的绝对URL。
  • urllib2 - 此模块对urllib模块进行了增加,增加了将url封装为Request的打开方式。
  • lxml - lxml是用于在Python语言中处理XML和HTML的最具功能和易于使用的库。
  • pandas 是一个数据分析库,可以对csv、excel等格式文件进行方便的读写。

明确目标

我们这次想要做的就是提取出糗百的文本板块的和糗事相关的数据,并结构化它们:作者、糗事的链接、作者的链接、作者的性别、糗事的内容、糗事的链接、好笑数、评论数。在这儿我们称为item,对应的python代码为:

item = ['author', 'author_href', 'author_sex', 'context', 'context_url', 'vote', 'comment']

解析HTML

python中解析HTML文件常用的库有三个:re(正则表达式库)、lxml 和 Beautiful Soup。三个库的特点如下:

名称 性能 使用难度 安装难度
re 困难 简单(内置)
Beautiful Soup 简单 简单(纯python)
Lxml 简单 相对困难

这儿我们选用Lxml这个库

选择器

我们使用xpath来提取数据。xpath学习可参考以下链接:

  • http://www.w3school.com.cn/xpath/index.asp
  • http://zvon.org/xxl/XPathTutorial/General_chi/examples.html
  • https://msdn.microsoft.com/zh-cn/library/ms256115(v=vs.80).aspx

HTML结构

我们观察糗百的文本模块,发现一页有20条糗事,我们可以先拿到包含20条糗事的模块的html代码,然后再从中解析出每条糗事的html代码,每条糗事的html代码类似于下面的结构:

今天美女同事对我说:“有件事我前几天就想问你了,你明天有空吗?”我心里一惊,莫非这是迟来的约会吗?于是赶快回答:“有空!”女同事感激道:“太好了,替我代一天班吧,我要出去约会!”额...
1177 好笑 · 4 评论

提取内容并保存

我们使用xpath来提取数据,代码如下:

# coding=utf-8
import re
import urlparse

import lxml.html
import pandas as pd
from link_crawler import download


def parse_item(html, url, item):
    """
    从html字符串中提取出结构化数据,然后返回rows
    :param html: 需要提取的html字符串
    :param url: 该html所对应的url
    :param item: 需要提取的字段
    :return:
    """
    # 存放当前页面的所有糗事模块的内容,子元素类型也为列表,内容为author,author_href,author_sex,context,vote,comment
    rows = []

    tree = lxml.html.fromstring(html)

    # 提取了一个包含当前页面的所有糗事模块列表,长度为20,对应当前页面的20条糗事模块
    frame = tree.xpath('//*[@id="content-left"]/div[@class="article block untagged mb15"]')

    sex_regex = re.compile('\s+(.*)Icon')

    for i in xrange(len(frame)):
        row = []

        # 提取作者名字、作者链接、作者性别
        author_module = frame[i].xpath('./div[@class="author clearfix"]')[0]
        try:
            author = author_module.xpath('./a[2]/@title')[0]
            author_href = urlparse.urljoin(url, author_module.xpath('./a[2]/@href')[0])
            author_sex = sex_regex.findall(author_module.xpath('./div/@class')[0])[0]
        except:
            author = author_module.xpath('.//h2/text()')[0]
            author_href = ''
            author_sex = ''
        row.append(author)
        row.append(author_href)
        row.append(author_sex)

        # 糗事内容和糗事链接
        context = unicode(frame[i].xpath('string(./a[1])')).strip()
        context_url = urlparse.urljoin(url, unicode(frame[i].xpath('string(./a[1]/@href)')))
        row.append(context)
        row.append(context_url)

        # 好笑数和评论数
        vote = frame[i].xpath('string(./div[@class="stats"]/span[@class="stats-vote"]/i)')
        comment = frame[i].xpath('string(./div[@class="stats"]/span[@class="stats-comments"]//i)')
        row.append(vote)
        row.append(comment)
        print dict(zip(item, row))
        rows.append(row)
    return rows


def save_csv(file_name, rows, columns):
    """
    将内容存为csv文件
    :param file_name: 写入的文件名
    :param rows: 写入的内容
    :param columns: 写入的字段
    :return:
    """
    data = pd.DataFrame(rows, columns=columns)
    data.to_csv(file_name, index=False, encoding='utf_8_sig')


if __name__ == "__main__":
    url = 'http://www.qiushibaike.com/text/'
    headers = {'User-Agent': 'crawl'}

    html = download(url, headers)

    file_name = 'qsbk_text.csv'
    item = ['author', 'author_href', 'author_sex', 'context', 'context_url', 'vote', 'comment']

    data = parse_item(html, url, item)
    save_csv(file_name, data, item)

其中 download 方法是上篇中的方法。运行如下:

Downloading url: http://www.qiushibaike.com/text/
{'comment': '40', 'author_sex': 'women', 'context_url': u'http://www.qiushibaike.com/article/118585314', 'author': u'\u54c7\u567b\uff5epeach', 'author_href': 'http://www.qiushibaike.com/users/30423399/', 'context': u'\u6700\u8fd1\u5728\u601d\u8003\u529e\u4e2a\u5065\u8eab\u623f\u7684\u4f1a\u5458\uff0c\u95fa\u871c\u77e5\u9053\u4e86\u8ddf\u6211\u8bf4:\u201c\u529e\u90a3\u4e2a\u5e72\u561b\uff1f\u8df3\u5e7f\u573a\u821e\u591a\u597d\uff0c\u8fd8\u4e0d\u65f6\u6709\u5927\u5988\u7ed9\u4f60\u4ecb\u7ecd\u5bf9\u8c61\u3002\u201d', 'vote': '2010'}
{'comment': '28', 'author_sex': 'man', 'context_url': u'http://www.qiushibaike.com/article/118584535', 'author': u'\u4e13\u4e1a^O^\u4e30\u80f8', 'author_href': 'http://www.qiushibaike.com/users/8324042/', 'context': u'\u521a\u521a\u9047\u5230\u7684\uff0c\u6211\u4eec\u5c0f\u533a\u4e00\u56db\u5c81\u5c0f\u5b69\u7279\u6dd8\u6c14\uff0c\u4eca\u5929\u88ab\u4ed6\u7238\u7238\u5988\u5988\u7237\u7237\u5976\u5976\u6bcf\u4eba\u6253\u4e86\u4e00\u987f\uff0c\u4e00\u4e2a\u4eba\u5728\u95e8\u5916\u561f\u56d4\uff1a\u2018\u8fd9\u5bb6\u4eba\u6ca1\u4e00\u4e2a\u597d\u4e1c\u897f\uff0c\u90fd\u6253\u6211\u2019\u2018\u5f53\u65f6\u6211\u5c31\u7b11\u55b7\u4e86\uff0c\uff0c\uff0c\uff0c', 'vote': '2005'}
....

然后使用excel打开 qsbk_text.csv 文件,可以看到类似于这样的内容:

1.4 爬虫修炼之道——从网页中提取结构化数据并保存(以爬取糗百文本板块所有糗事为例)_第1张图片
结构化数据

设置回调函数

上篇我们讲解了如何下载多个页面(URL),在这里我们可以将上篇内容和这篇内容结合起来,每当下载一个URL后,就调用我们的parse_item方法。最后将所有页面的item

想要完成以上功能,需要修改上篇中的 link_crawler 方法。修改后的 link_crawler 如下:

def link_crawler(seed_url, link_regex, delay=2, user_agent='crawl', headers=None, proxy=None, num_retries=2, time_out=3, item=None, callback=None):
    """
    下载一个URL,提取出给定的item,然后根据link_regex规则跟进链接并提取出来跟进链接中的item
    :param seed_url: 种子URL
    :param link_regex: 从种子URL页面中提取跟进链接所用的正则表达式
    :param delay: 爬取同一域下的URL暂停时间
    :param user_agent: 用户代理
    :param headers: 头
    :param proxy: 代理
    :param num_retries: 下载一个页面失败后的重试次数
    :param time_out: 下载一个URL的超时时间
    :param item: 需要提取的item
    :param callback: 回调函数,用于提取item
    :return: 如果callback和item不为None,则返回一个item列表,否则返回空列表
    """
    item_list = []
    crawl_queue = [seed_url]  # 需要下载的URL列表

    throttle = Throttle(delay)  # 限速器

    headers = headers or {}
    if user_agent:
        headers['User-agent'] = user_agent

    while crawl_queue:
        url = crawl_queue.pop()  # 将种子URL弹出

        html = download(url, headers, proxy, num_retries, time_out)  # 下载当前URL页面

        if callback:
            items = callback(html, url, item)
            item_list.extend(items)

        throttle.wait(url)  # 根据该url所对应的域来决定是否需要暂停delay秒

        links = get_links(html, link_regex)  # 得到当前URL页面的跟进链接
        print 'links: %s' % links
        for link in links:
            link = urlparse.urljoin(seed_url, link)
            crawl_queue.append(link)

    return item_list

测试运行

代码github地址:https://github.com/Oner-wv/spider_note/tree/master/chapter02

现在来运行整个代码

link_regex = re.compile('
  • .*', re.S) item = ['author', 'author_href', 'author_sex', 'context', 'context_url', 'vote', 'comment'] data = link_crawler('http://www.qiushibaike.com/text/', link_regex, delay=1, user_agent='crawl', num_retries=2, time_out=3, item=item, callback=parse_item) file_name = "qsbk_text_full.csv" save_csv(file_name, data, item)
  • 运行后打开qsbk_text_full.csv文件。一共有701行数据,除去头文件,共700行,35 * 20 = 700 ,35为页数,20为每页的糗事条数,说明已经将糗事百科的文本板块下的数据全部爬取下来了。

    下篇我们将讲解如何爬取图片 爬虫修炼之道——从网页中下载所需图片(以下载糗百热图为例)

    更多内容请关注公众号:AI派

    1.4 爬虫修炼之道——从网页中提取结构化数据并保存(以爬取糗百文本板块所有糗事为例)_第2张图片

    你可能感兴趣的:(1.4 爬虫修炼之道——从网页中提取结构化数据并保存(以爬取糗百文本板块所有糗事为例))