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