从零开始写一只爬虫 · 排排坐吃果果

从零开始写一只爬虫
这里需要用到 队列 和 宽度优先搜索(BFS)

什么是队列?

(详细说明请翻阅 维基百科

你去食堂打饭,人多,要排队是吧?队伍头部的人正在被服务(打饭),新来的人只能跑到队伍最后面,眼巴巴地看着。队头的人打完饭了,就离开队伍,座到旁边吃去了。此时第二名变成了对头,开始打饭……

也就是说,新来的只能加在队伍最后面,而队头才是我们永远的候选。选中它,操作它,扔掉它。

Python 的列表很魔性啊!数组、队列、栈三合一!很神奇!怪不得列表是 Python 的 “苦力”。

那么宽度优先搜索呢?

(详细说明请翻阅 维基百科

队头的人打完饭,拿出手机,给室友们打电话:“喂,想吃饭的话自己来打饭,老子不给你们带饭了”。室友们接到电话,立刻闪现过来,然而无奈发现只能去队伍后面默默排队,顺带看着刚才 Call 自己的那位猪队友优雅地离开队伍,坐旁边吃去了。

第二个人也是如此。队伍好像变长了。

第三个人木有室友……

第四个人打了电话,然而室友叫了外卖,不来打饭了……

总之呢,队伍总会排完的。

ok,爬虫也是这样,拿到一个页面之后,在抓取下自己感兴趣的东西之后,顺带着将这个页面中的所有超链接都找了出来,记录下来,加入到一个队列里面。然后,只要队列不空,就取出队头,访问这个 URL,继续抓取、找出链接、放入队列……

在这里我们先考虑一种简单的情况:事先就能把全部的网址都构造出来,放入队列中,不考虑队列会在抓取过程中被动态地添加进去新的 URL。这样便于测试,并且在大多数时候是很有效的。

为什么?仔细看 URL,对于一部分网站来说,URL 都是有规律的:

http://www.lagou.com/gongsi/129.html

把后面的 129 换成其他数字试试?发现了什么?

另外还有一个好处:队伍长度一直在减少。这样就不会出现 “一个网页被访问多次” 的尴尬局面了。至于 “将网址动态添加到队列中” 的情形,我们以后再讨论。

排排坐

这个不难,一个 for 循环就全都出来了:

queue=[]
for index in range(100,200):
    url='http://www.lagou.com/gongsi/'+str(index)+'.html'
    queue.append(url)

吃果果

当队列不为空的时候,取出队头,进行抓取:

while queue:
    url = queue.pop(0)
    response = requests.get(url, timeout=5, allow_redirects=False)
    data = BeautifulSoup(response.text)
    ... ...

当然,我们构造出来的 URL 有可能会导致 404。因为我们是尝试打开了几个网页后就假设它的 id 号是连续增加的。如果 404 了怎么办?

跳过啊!这还用教!

也就是说,如果网页返回 404, 或者从返回的网页中得不到我们想要的信息,跳过就好啦~天涯何处无芳草,少你一个不算少!吼吼!

import requests
from bs4 import BeautifulSoup

queue = []
for index in range(100, 200):
    url = 'http://www.lagou.com/gongsi/' + str(index) + '.html'
    queue.append(url)

while queue:
    url = queue.pop(0)
    try:
        response = requests.get(url, timeout=5, allow_redirects=False) #有可能在这里,因为网络原因,导致超时或者其他什么
        data = BeautifulSoup(response.text)
        name = data.h1.a.text.strip() #有可能在这里,因为没有这个公司,导致 404, 获取不到信息
        location = data.find('ul', 'info_list_with_icon').find(attrs={'class': 'location'}).span.text
        print("%s %s" % (name, location))
    except:
        pass

写到这里,对于一个洁癖来说,我已经快受不了了。随着代码越来越多,不把各个功能都分成函数的话,我会疯掉的。

OK,下面是代码重写后的结果。

import requests
from bs4 import BeautifulSoup


class Spider:
    def __init__(self):
        self.queue = []

    def addUrlBlock(self, start=100, end=200):
        for index in range(start, end):
            url = 'http://www.lagou.com/gongsi/' + str(index) + '.html'
            self.addUrl(url)

    def addUrl(self, url):
        self.queue.append(url)

    def run(self):
        while self.queue:
            url = self.queue.pop(0)
            try:
                response = requests.get(url, timeout=5, allow_redirects=False)
                data = BeautifulSoup(response.text)
                name = data.h1.a.text.strip()
                location = data.find('ul', 'info_list_with_icon').find(attrs={'class': 'location'}).span.text
                print("%s %s" % (name, location))
            except:
                pass


if __name__ == '__main__':
    spider = Spider()
    spider.addUrlBlock(100, 200)
    spider.run()

看起来舒 (zhuang) 服 (bi) 多了吧?

留下评论