乐趣区

一次爬虫的并发改造过程

事情是这样的:因为在写一个豆瓣抽奖的小程序,我需要抓取豆瓣广播所有转发的用户信息,然后从这些用户里面抽取幸运观众。

典型的 IO 密集型操作呀。

首先爬虫要进入广播找到一共有多少转发页,大概长这样:

def get_pages(url):
    r = fetch(url)
    
    soup = BeautifulSoup(r, 'lxml')
    page_num = soup.find("div", class_="paginator").find_all("a")[-2].string

    return page_num

然后依次解析每一页的用户信息:

def parse_one(r):
    human = list()
    soup = BeautifulSoup(r, 'lxml')
    try:
        content = soup.find_all("li", class_="list-item")
    except:
        logging.error("there is no info.")
        raise
        .....

    return human

很简单吧。

刚开始我用 requests 库直接依次请求每一页的内容,代码写好后点击执行,等啊等,等啊等,终于爬完了,控制台输出:

耗时: 106.168...

我两眼一抹黑,要是用户用这个来抽奖,黄花菜都凉了。

遂决定多开几个进程,使用multiprocessing.Pool() 创建多个进程,看看效果:

pool = multiprocessing.Pool(5)
url_list = [url + '?start={}&tab=reshare#reshare'.format(i * 20) for i in range(0, int(page_num))]
with pool as p:
    res = p.map(parse_one, url_list)

耗时:

耗时: 26.168740749359131

好像还不错哎。比之前顺序爬取要快了不少。但还是有点慢。

然后我决定上 携程 (协程)。

首先获取网页内容肯定要异步获取:

async def fetch(url):
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50",
        ......
    }
    async with aiohttp.ClientSession(headers=headers) as session:
        async with session.get(url) as res:
            result = await res.text()
            return result

然后将所有请求放入 task 中:

url_list = [redict_url + '?start={}&tab=reshare#reshare'.format(i * 20) for i in range(0, int(page_num))]
loop = asyncio.=get_event_loop()
tasks = [asyncio.ensure_future(reshare(url)) for url in url_list]
loop.run_until_complete(asyncio.wait(tasks))

for task in tasks:
    human.extend(task.result())

接下来就是见证奇迹的时刻了!

(然而并不是,豆瓣把我封了。我太难了。)

那就上代理 IP 吧。又写了个爬虫爬了几百个代理 ip 地址。每次请求时候随机选择代理:

def random_proxy():
    url = hosts[random.randint(1, len(hosts))]
    proxy = "http://{}".format(url)
    return proxy

现在来看看效果:

耗时: 2.2529048919677734

Amazing!!!

关于线程,进程,协程,参考我之前写的文章:

对 python 并发编程的思考

另外,

我的豆瓣抽奖小程序(豆酱)已经上线了,欢迎使用提 bug 呀。

退出移动版