Python爬虫-速度(3)

Python爬虫-速度(3)


018.11.11

Python爬虫-速度(1)
Python爬虫-速度(2)

前言

早之前是以为会一口气把爬虫这个系列了结的,但不知何故,居然没做到——我肯定不会怪罪自己的拖延症呀!只是有开头就得有结尾,毕竟我是那么那么注重仪式感的人。

再从GitHub把代码clone下来,发现不能用了。刨根问底,居然是学校就业网站改版,这倒令我惊奇。会是因为哪个老师或者学生确实无聊所以动了“美好校园”的念头?缘由于我来说肯定是不得而知的。

重写一份吧。结构上相比两个月前的代码是有所优化的,但不至于天壤之别。相似处多,所以不再单独对此次基础代码进行说明。

此篇目的是:基于(2)的提速方法,改写(1)爬虫代码。当然了,因为网页改版,接下来我会是基于新的代码来改写。

那就开始啦。

普通爬虫

此次代码对应目录:NewSchoolJobsCrawl

效果如下:Python爬虫-速度(3)

Python爬虫-速度(3)

多次运行,整个程序跑完在60s左右。
Python爬虫-速度(3)

多进程提速

在ProcessPoolExecutor助攻下,多进程爬虫的实现真的会很简单。我们只需要把浏览页处理的逻辑提取出来(我把它放到了main()中),然后构建一个进程池去执行这部分的代码就好。这样说会很抽象,将下面的代码与普通爬虫的代码做对比,理解起来容易些。

def main(url):
    html, __ = get_one_html(url)
    if html:
        for title, url in extract_title_url(html):
            # 只保留公司名
            companyName = re.split(r"(|\(|\s", title)[0]
            mkdir(companyName)
            # 详情页面,详情页链接
            detailHtml, curUrl = get_one_html(join_url(url))
            # 详情页招聘信息,doc链接
            positionInfo, docLink = extract_detail_data(detailHtml)
            save_to_text(companyName, positionInfo)
            download_doc(companyName, docLink, curUrl)
            print(companyName, curUrl)

if __name__ == "__main__":
    start = time.time()
    init()
    # fstring python3.7特性
    suffix = (f"/{i}" for i in range(1, 23))

    urls = [re.sub(r".htm", str(page)+".htm", MAIN_URL) for page in chain([""], suffix)]
    with ProcessPoolExecutor() as pool:
        pool.map(main, urls)

    end = time.time()
    takeTime = end - start
    print(f"【总共耗时】{takeTime}")

平均耗时:10s上下

多线程提速

同样的,有了ThreadPoolExecutor后,多线程也变得简单起来。除了类名,其他部分可以完全和多进程的代码一样。

from concurrent.futures import ThreadPoolExecutor

...

if __name__ == "__main__":
	...
    with ThreadPoolExecutor() as pool:
        pool.map(main, urls)
    ...

平均耗时:9s+

异步协程提速

异步协程的改动会大一些,这是因为requests模块不支持异步操作。我们需要借助aiohttp来封装一个get请求方式的get()方法如下:

async def get(url, referer=None):
    headers = HEADERS
    if referer is not None:
        r = {"Referer": str(referer)}
        headers.update(r)

    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as response:
            text = await response.read()
            url = response.url
            assert response.status == 200

    return text, url

通过这个方法,就可以实现异步操作了。

其他地方也需要做相应的调整,比如async修饰的函数,调用时需要await关键字,但都是小问题。更多需要在意协程是如何使用的。

if __name__ == "__main__":
    start = time.time()
    init()
    # fstring python3.7特性
    suffix = (f"/{i}" for i in range(1, 23))

	# 注意这一部分的改动
    urls = [re.sub(r".htm", str(page)+".htm", MAIN_URL) for page in chain([""], suffix)]
    tasks = [asyncio.ensure_future(main(url)) for url in urls]

    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
    # --------注意线-------------

    end = time.time()
    takeTime = end - start
    print(f"【总共耗时】{takeTime}")

平均耗时:9s+

最后

感觉这一次写得很“潦草”,这是没办法的事儿。因为以上三个内容(多进程,多线程,协程)每一个都可以拿出来单独写好长一篇。可我这个系列是:爬虫。为了不客夺主位,只好一切从简。

但无论如何是有说到的爬虫提速的,让我的爬虫系列有头有尾——虽然虎头蛇尾。

这次展示的三个提速代码,并非将速度达到极致,许多地方还可以优化,但我并不建议如此。因为出于兴趣爱好的爬虫,真的没有必要分秒必争,况且网站的运维人员也需要休息、娱乐,也需要陪陪家人。而不是一直待公司跟你玩儿“猫捉老鼠”吧!

完整代码已经上传GitHub