美文网首页IT技术python学习WEB前端程序开发
1.4 爬虫修炼之道——从网页中提取结构化数据并保存(以爬取糗百

1.4 爬虫修炼之道——从网页中提取结构化数据并保存(以爬取糗百

作者: 王伟_同学 | 来源:发表于2017-02-20 09:13 被阅读1428次

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

    上篇 爬虫修炼之道——编写一个爬取多页面的网络爬虫主要讲解了如何使用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学习可参考以下链接:

    HTML结构

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

    <div class="article block untagged mb15" id="qiushi_tag_118589872">
    
    <div class="author clearfix">
    <a href="/users/31146403/" target="_blank" rel="nofollow">
    ![飞o鸟](http:https://img.haomeiwen.com/i4758863/d37f49f1d7e27422.JPEG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    </a>
    <a href="/users/31146403/" target="_blank" title="飞o鸟">
    <h2>飞o鸟</h2>
    </a>
    <div class="articleGender manIcon">28</div>
    </div>
    
    
    
    <a href="/article/118589872" target="_blank" class="contentHerf">
    <div class="content">
    
    
    
    <span>今天美女同事对我说:“有件事我前几天就想问你了,你明天有空吗?”我心里一惊,莫非这是迟来的约会吗?于是赶快回答:“有空!”女同事感激道:“太好了,替我代一天班吧,我要出去约会!”额...</span>
    
    
    </div>
    </a>
    
    
    
    
    
    <div class="stats">
    <span class="stats-vote"><i class="number">1177</i> 好笑</span>
    <span class="stats-comments">
    
    
    <span class="dash"> · </span>
    <a href="/article/118589872" data-share="/article/118589872" id="c-118589872" class="qiushi_comments" target="_blank">
    <i class="number">4</i> 评论
    </a>
    
    
    
    </span>
    </div>
    <div id="qiushi_counts_118589872" class="stats-buttons bar clearfix">
    <ul class="clearfix">
    <li id="vote-up-118589872" class="up">
    <a href="javascript:voting(118589872,1)" class="voting" data-article="118589872" id="up-118589872" rel="nofollow">
    <i></i>
    <span class="number hidden">1192</span>
    </a>
    </li>
    <li id="vote-dn-118589872" class="down">
    <a href="javascript:voting(118589872,-1)" class="voting" data-article="118589872" id="dn-118589872" rel="nofollow">
    <i></i>
    <span class="number hidden">-15</span>
    </a>
    </li>
    
    <li class="comments">
    <a href="/article/118589872" id="c-118589872" class="qiushi_comments" target="_blank">
    <i></i>
    </a>
    </li>
    
    </ul>
    </div>
    <div class="single-share">
    <a class="share-wechat" data-type="wechat" title="分享到微信" rel="nofollow">微信</a>
    <a class="share-qq" data-type="qq" title="分享到QQ" rel="nofollow">QQ</a>
    <a class="share-qzone" data-type="qzone" title="分享到QQ空间" rel="nofollow">QQ空间</a>
    <a class="share-weibo" data-type="weibo" title="分享到微博" rel="nofollow">微博</a>
    </div>
    <div class="single-clear"></div>
    
    
    
    
    </div>
    

    提取内容并保存

    我们使用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 文件,可以看到类似于这样的内容:

    结构化数据

    设置回调函数

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

    想要完成以上功能,需要修改上篇中的 link_crawler 方法。修改后的 <a id="link_crawler" href="#link_crawler">link_crawler</a> 如下:

    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('<li>.*<a\s+href="(.*?)".*<span class="next">', 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为每页的糗事条数,说明已经将糗事百科的文本板块下的数据全部爬取下来了。

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

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

    相关文章

      网友评论

        本文标题:1.4 爬虫修炼之道——从网页中提取结构化数据并保存(以爬取糗百

        本文链接:https://www.haomeiwen.com/subject/gjxjwttx.html