爬取1000条百度百科词条

爬虫是什么?

爬虫是一段自动抓取互联网信息的程序,从而可以更好利用数据进行相关分析,做出相关决策。

简单的爬虫架构

结构图

如上图所示,这个架构主要分成五个部分:

  1. 爬虫总调度器
    主要用来调度其他各个模块互相协作获取到我们想要的数据
  2. url管理器
    这里面主要管理两部分数据:尚未爬取的url和等待爬取的url数据
  3. 下载器
    从url管理器中获取尚未爬取的ulr,使用urllib中的request模块爬取网络数据。
  4. 解析器
    从下载器中获取的html数据解析出新的url和我们想提取的数据。
  5. 输出器
    将我们想要的数据输出到文件中。

使用

  1. 先看url管理器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2018-07-15 15:25:55
# @Author : zhulei (zhuleimailname@gmail.com)
# @Link : http://zhuleiblog.com


class UrlManager(object):

def __init__(self):
self.new_urls = set()
self.old_urls = set()

def add_new_url(self, url):
if url is None:
return
if url not in self.new_urls and url not in self.old_urls:
self.new_urls.add(url)

def add_new_urls(self, urls):
if urls is None or len(urls) == 0:
return
for url in urls:
self.add_new_url(url)

def has_new_url(self):
return len(self.new_urls) != 0

def get_new_url(self):
new_url = self.new_urls.pop()
self.old_urls.add(new_url)
return new_url

这个模块主要作用是管理带爬取和以爬取的url,其中new_urls是指待爬取的url,old_urls是指已经爬取的url,数据结构都是set,可以直接去重。

  1. 下载器:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # @Date : 2018-07-15 15:25:01
    # @Author : zhulei (zhuleimailname@gmail.com)
    # @Link : http://zhuleiblog.com

    from urllib import request


    class HtmlDownloader(object):
    """Html下载器"""
    def download(self, url):
    if url is None:
    return None

    response = request.urlopen(url)
    if response.getcode() != 200:
    return None

    return response.read()

这个就比较简单了,直接使用urllib中的request模块爬取数据

  1. 解析器:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # @Date : 2018-07-15 15:25:31
    # @Author : zhulei (zhuleimailname@gmail.com)
    # @Link : http://zhuleiblog.com
    import re
    from urllib import parse
    from bs4 import BeautifulSoup


    class HtmlParser(object):

    def _get_new_urls(self, page_url, soup):
    new_urls = set()

    # <a target="_blank" href="/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E8%AF%AD%E8%A8%80">计算机程序设计语言</a>
    links = soup.find_all('a', href=re.compile(r"/item/"))
    for link in links:
    new_url = link['href']
    new_full_url = parse.urljoin(page_url, new_url)
    new_urls.add(new_full_url)
    return new_urls

    def _get_new_data(self, page_url, soup):
    res_data = {}

    # url
    res_data['url'] = page_url

    # <dd class="lemmaWgt-lemmaTitle-title">
    # <h1>Python</h1>
    # <h2>(计算机程序设计语言)</h2>
    # <a href="javascript:;" class="edit-lemma cmn-btn-hover-blue cmn-btn-28 j-edit-link" style="display: inline-block;"><em class="cmn-icon wiki-lemma-icons wiki-lemma-icons_edit-lemma"></em>编辑</a>
    # <a class="lock-lemma" target="_blank" href="/view/10812319.htm" title="锁定"><em class="cmn-icon wiki-lemma-icons wiki-lemma-icons_lock-lemma"></em>锁定</a>
    # </dd>
    title_node = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1')
    res_data['title'] = title_node.get_text()

    # <div class="lemma-summary" label-module="lemmaSummary">
    # <div class="para" label-module="para">Python<sup class="sup--normal" data-sup="1">
    # [1]</sup><a class="sup-anchor" name="ref_[1]_21087">&nbsp;</a>
    # (英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/), 是一种面向对象的解释型<a target="_blank" href="/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E8%AF%AD%E8%A8%80">计算机程序设计语言</a>,由荷兰人<a target="_blank" href="/item/Guido%20van%20Rossum">Guido van Rossum</a>于1989年发明,第一个公开发行版发行于1991年。</div><div class="para" label-module="para">Python是纯粹的<a target="_blank" href="/item/%E8%87%AA%E7%94%B1%E8%BD%AF%E4%BB%B6/405190" data-lemmaid="405190">自由软件</a>, <a target="_blank" href="/item/%E6%BA%90%E4%BB%A3%E7%A0%81/3969" data-lemmaid="3969">源代码</a>和<a target="_blank" href="/item/%E8%A7%A3%E9%87%8A%E5%99%A8">解释器</a>CPython遵循 <a target="_blank" href="/item/GPL">GPL</a>(<a target="_blank" href="/item/GNU">GNU</a> General Public License)协议。Python语法简洁清晰,特色之一是强制用空白符(white space)作为语句缩进。</div><div class="para" label-module="para">Python具有丰富和强大的库。它常被昵称为<a target="_blank" href="/item/%E8%83%B6%E6%B0%B4%E8%AF%AD%E8%A8%80/3564482" data-lemmaid="3564482">胶水语言</a>,能够把用其他语言制作的各种模块(尤其是<a target="_blank" href="/item/C/7252092" data-lemmaid="7252092">C</a>/<a target="_blank" href="/item/C%2B%2B">C++</a>)很轻松地联结在一起。常见的一种应用情形是,使用Python快速生成程序的原型(有时甚至是程序的最终界面),然后对其中有特别要求的部分,用更合适的语言改写,比如<a target="_blank" href="/item/3D%E6%B8%B8%E6%88%8F">3D游戏</a>中的图形渲染模块,性能要求特别高,就可以用<a target="_blank" href="/item/C%2FC%2B%2B/6824246" data-lemmaid="6824246">C/C++</a>重写,而后封装为Python可以调用的扩展类库。需要注意的是在您使用扩展类库时可能需要考虑平台问题,某些可能不提供<a target="_blank" href="/item/%E8%B7%A8%E5%B9%B3%E5%8F%B0/8558902" data-lemmaid="8558902">跨平台</a>的实现。</div><div class="para" label-module="para">7月20日,<a target="_blank" href="/item/IEEE/150905" data-lemmaid="150905">IEEE</a>发布2017年编程语言排行榜:Python高居首位<sup class="sup--normal" data-sup="2">
    # [2]</sup><a class="sup-anchor" name="ref_[2]_21087">&nbsp;</a>
    # 。</div><div class="para" label-module="para">2018年3月,该语言作者在邮件列表上宣布 Python 2.7将于2020年1月1日终止支持。用户如果想要在这个日期之后继续得到与Python 2.7有关的支持,则需要付费给商业供应商。<sup class="sup--normal" data-sup="3">
    # [3]</sup><a class="sup-anchor" name="ref_[3]_21087">&nbsp;</a>
    # </div>
    # </div>
    summary_node = soup.find('div', class_='lemma-summary').find('div', class_='para')
    res_data['summary'] = summary_node.get_text()
    return res_data

    def parse(self, page_url, html_content):
    if page_url is None or html_content is None:
    return

    soup = BeautifulSoup(html_content, 'html.parser', from_encoding='utf-8')
    new_urls = self._get_new_urls(page_url, soup)
    new_data = self._get_new_data(page_url, soup)
    return new_urls, new_data

这个模块主要是将下载器的数据进行解析,解析使用的是「BeautifulSoup」这个第三方库,如果没有安装的话,请先安装:
pip install beautifulsoup4
这里需要分析我们需要爬取的数据内容,我这里想爬取的内容是:百科词条的标题和简介
标题和简介

首先我们要先看下标题的html标签格式是什么样的,右击标题「Python」-> 检查,可以看到它的标签如下:

1
2
3
4
5
6
# <dd class="lemmaWgt-lemmaTitle-title">
# <h1>Python</h1>
# <h2>(计算机程序设计语言)</h2>
# <a href="javascript:;" class="edit-lemma cmn-btn-hover-blue cmn-btn-28 j-edit-link" style="display: inline-block;"><em class="cmn-icon wiki-lemma-icons wiki-lemma-icons_edit-lemma"></em>编辑</a>
# <a class="lock-lemma" target="_blank" href="/view/10812319.htm" title="锁定"><em class="cmn-icon wiki-lemma-icons wiki-lemma-icons_lock-lemma"></em>锁定</a>
# </dd>

可以看出是在dd标签中class属性为lemmaWgt-lemmaTitle-titleh1标签中,所以代码很简单:

1
2
title_node = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1')
res_data['title'] = title_node.get_text()

同理可以找出简介的标签规律。

  1. 输出器:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class HtmlOutputer(object):
    def __init__(self):
    self.datas = []

    def collect_data(self, data):
    if data is None:
    return
    self.datas.append(data)


    def output_html(self):
    f_out = None
    try:
    f_out = open('output.html', 'w', encoding='utf-8')

    f_out.write("<html>")
    f_out.write("<body>")
    f_out.write("<table>")

    for data in self.datas:
    f_out.write("<tr>")
    f_out.write("<td style=\"width:500;word-break:break-all\">%s</td>" % data['url'])
    f_out.write("<td style=\"width:150;word-break:break-all\">%s</td>" % data['title'])
    f_out.write("<td>%s</td>" % data['summary'])

    f_out.write("</table>")
    f_out.write("</body>")
    f_out.write("</html>")
    finally:
    if f_out:
    f_out.close()

这个模块也很简单,主要是存储我们想要的数据,并将其以html中表格的格式输出到「output.html」这个文件中,不过要注意一点由于爬取的内容有中文,所以一定要指定encodingutf-8,不然会出现乱码。

  1. 最后我们写好我们的调度器,进行爬取1000条词条数据:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # @Date : 2018-07-15 15:23:26
    # @Author : zhulei (zhuleimailname@gmail.com)
    # @Link : http://zhuleiblog.com
    from baike_spider import url_manager, html_downloader, html_parser, html_outputer


    class Spider(object):
    def __init__(self):
    self.url_manager = url_manager.UrlManager()
    self.downloader = html_downloader.HtmlDownloader()
    self.html_parser = html_parser.HtmlParser()
    self.html_outputer = html_outputer.HtmlOutputer()

    def craw(self, rootUrl):

    count = 1
    self.url_manager.add_new_url(rootUrl)
    while self.url_manager.has_new_url():
    try:

    new_url = self.url_manager.get_new_url()
    print("craw%d -> %s" % (count, new_url))
    content = self.downloader.download(new_url)
    new_urls, data = self.html_parser.parse(new_url, content)
    self.url_manager.add_new_urls(new_urls)
    self.html_outputer.collect_data(data)

    if count == 1000:
    break
    count = count + 1

    except:
    print('craw failed')

    self.html_outputer.output_html()


    root_url = "https://baike.baidu.com/item/Python/407313"

    if __name__ == "__main__":
    spider = Spider()
    spider.craw(root_url)

起始url为Python词条的url:root_url = "https://baike.baidu.com/item/Python/407313"
好了,运行调度器,开始爬取数据吧。

看下最后运行的结果:

存储的文件是html文件,找到文件路径使用浏览器打开可以看到如下图:

百科爬取词条

源码链接