How to Write a Spider

找到爬取目标

要想通过机器爬取数据,必须先知道数据在哪,通过什么样的方式获取数据。

一般先打开要爬取的网站,再通过审查元素的方式查看要爬取网站的网页结构。选取要爬取数据最具特点的内容作为搜索标记(例如下载论文的img),然后制定爬取规则进行爬取。

制定爬取规则是整个爬虫过程最为重要的过程,好的爬取规则可以事半功倍。

例如,在我的爬虫项目中,我们要爬取的目标对象附近总会有一个”PDF”的图像 pdf_logo,而其他部分没有这个图像pdf_logo,我们就可以使用这个图像作为我爬取规则的关键字。(这种提取关键字的思想可以参考TF-IDF)

使用好工具

对于一般的小型爬虫项目,使用Request库+BeautifulSoup库是非常方便的。Request库为Python提供一个使用HTTP协议的API,BeautifulSoup库为Python处理HTML格式文件的API,两者结合可以快速完成一般的网络爬取项目。

Requests 允许你发送纯天然,植物饲养的 HTTP/1.1 请求,无需手工劳动。你不需要手动为
URL 添加查询字串,也不需要对 POST 数据进行表单编码。Keep-alive 和 HTTP 连接池的功能是
100% 自动化的,一切动力都来自于根植在 Requests 内部的 urllib3。(更多介绍参考Request中文文档

Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间。(更多介绍参考BeautifulSoup中文文档

编程模板
  1. 引入所需要的库

    1
    2
    3
    import requests #Request库,用以使用HTTP协议
    from bs4 import BeautifulSoup # BeautifulSoup库,用以解析HTML格式文件
    import time #Time库,用以延时下载(sleep(5)反反爬)

    其中Time库,用以引入sleep()函数,如果目标网站通过访问间隔进行限制用户访问,一般爬取很容易爬到403禁止访问页面。因此我们需要sleep()函数进行反反爬。

  2. 获取目标页面并解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def GetPage():
    try:
    url = "yoursite.com" # 你目标网站的URL
    headers = {'user-agent': 'Mozilla/5.0'} # 修改头文件欺骗目标站点
    r = requests.get(url=url,headers=headers) # 构建HTTP数据包并发送给目标站点
    r.raise_for_status() # 检查是否返回正确页面
    r.encoding = r.apparent_encoding # 使用最有可能的解码方式进行解码
    return r # 返回爬到的数据
    except:
    return "产生异常"

    if __name__ == '__main__':
    r = GetPage()
    soup = BeautifulSoup(r.text,'html.parser') # 将爬到的数据让BeautifulSoup以html格式解码

    本项目是爬取某学术会议的论文集,所以并不需要扩散式的爬取,仅需将某页面上的所有论文PDF及其题目摘要爬取下来。

  3. 根据具体的爬取规则实现爬取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    for pdf in soup.find_all('img'): # 上接 main 函数
    if pdf['src'] == "imagetypes/pdf_logo.gif":

    PdfFather = pdf.parent.parent.parent.parent
    # 找到论文PDF下载地址
    DownloadLink=pdf.parent["href"]

    # 找到论文的题目
    TitleFather = PdfFather.previous_sibling
    Title = GetText(TitleFather.get_text())[0]

    # 找到论文的摘要
    AbstractFather = PdfFather.next_sibling
    AbstractFather = PdfFather.next_sibling
    Abstract = 0
    for AbstractFather in PdfFather.next_siblings:
    if str(type(AbstractFather)) =="<class 'bs4.element.NavigableString'>":
    continue
    temp = GetText(AbstractFather.get_text()) # 将爬到的摘要前后的空格与回车去掉
    Abstract = temp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    def GetText(temp):# 将爬到的摘要前后的空格与回车去掉
    first = -1
    rear = 0
    for i in range(len(temp)):
    if temp[i] == '\n' or temp[i] == ' ':
    continue
    else:
    first = i
    break
    for i in range(len(temp)):
    if temp[-(i + 1)] == '\n' or temp[-(i + 1)] == ' ':
    continue
    else:
    rear = -(i + 1)
    break
    temp = temp[first:rear + 1]
    temp = temp.split("\n")
    return temp
  4. 将爬到的东西下载到本地

    1
    2
    3
    4
    5
    6
    7
    8
    headers = {'user-agent': 'Mozilla/5.0'}
    time.sleep(5)
    r = requests.get(url=DownloadLink,headers=headers)
    with open(Title+".pdf", "wb") as code:
    code.write(r.content)
    if Abstract!=None:
    with open(Title + ".txt", "w",encoding='utf-8') as code:
    code.write(Abstract)
常见错误:
  1. 被反爬

    由于利用Request库进行GET操作时,HTTP报头会表示客户端为Python,所以很容易被针对。检测HTTP报头Agent字段是最简单的反爬机制,如果目标网站还有其他反爬机制,请参考更多文献。

    在本项目中,我们将Agent改为“Mozilla/5.0”即可越过反爬机制。具体代码如下:

    1
    2
    3
    url = "yoursite.com"
    headers = {'user-agent': 'Mozilla/5.0'}
    r = requests.get(url=url,headers=headers)
  2. 编码出错

    由于window新建文件的默认编码为GBK,而Request库爬到的数据默认编码为UTF-8,虽然在写入文件时Python会自动将UTF8码转为GBK,但是一些特殊字符是不能转换成功的。因此我们应以UTF8格式新建文件。

    1
    2
    with open(Title + ".txt", "w",encoding='utf-8') as code:
    code.write(Abstract)
  3. BeautifulSoup使用规范

    在BeautifulSoup中,可以跨标签寻找子孙,只要我们知道目标标签的标签类型。

    例如:通过点取属性的方式能获得当前名字的第一个tag:

    1
    2
    soup.a
    # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

    但是,在BeautifulSoup中不能跨标签使用.string和.strings参数。

    例如:如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None :

    1
    2
    print(soup.html.string)
    # None

幸运的是,我们可以使用get_text()方法获取非叶子结点的所有NavigableString 子孙,并返回一个连接到一起的字符串(而非字符串的集合)。

例如,如果tag中包含多个字符串 [2] ,可以使用 .strings 来循环获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for string in soup.strings:
print(repr(string))
# u"The Dormouse's story"
# u'\n\n'
# u"The Dormouse's story"
# u'\n\n'
# u'Once upon a time there were three little sisters; and their names were\n'
# u'Elsie'
# u',\n'
# u'Lacie'
# u' and\n'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# u'...'
# u'\n'