本文首发于
本文是的续篇,实现基于多线程的 翻页、抓取二级页面。使用豆瓣top250作为例子,为了防止请求过快ip被封,我们每页只抓取5个电影。
爬虫代码如下
import requestsimport timefrom threading import Threadfrom queue import Queueimport jsonfrom bs4 import BeautifulSoupdef run_time(func): def wrapper(*args, **kw): start = time.time() func(*args, **kw) end = time.time() print('running', end-start, 's') return wrapperclass Spider(): def __init__(self): self.start_url = 'https://movie.douban.com/top250' self.qurl = Queue() self.data = list() self.item_num = 5 # 限制每页提取个数(也决定了二级页面数量)防止对网页请求过多 self.thread_num = 10 # 抓取二级页面线程数量 self.first_running = True def parse_first(self, url): print('crawling', url) r = requests.get(url) soup = BeautifulSoup(r.content, 'lxml') movies = soup.find_all('div', class_ = 'info')[:self.item_num] for movie in movies: url = movie.find('div', class_ = 'hd').a['href'] self.qurl.put(url) nextpage = soup.find('span', class_ = 'next').a if nextpage: nexturl = self.start_url + nextpage['href'] self.parse_first(nexturl) else: self.first_running = False def parse_second(self): while self.first_running or not self.qurl.empty(): url = self.qurl.get() print('crawling', url) r = requests.get(url) soup = BeautifulSoup(r.content, 'lxml') mydict = {} title = soup.find('span', property = 'v:itemreviewed') mydict['title'] = title.text if title else None duration = soup.find('span', property = 'v:runtime') mydict['duration'] = duration.text if duration else None time = soup.find('span', property = 'v:initialReleaseDate') mydict['time'] = time.text if time else None self.data.append(mydict) @run_time def run(self): ths = [] th1 = Thread(target=self.parse_first, args=(self.start_url, )) th1.start() ths.append(th1) for _ in range(self.thread_num): th = Thread(target=self.parse_second) th.start() ths.append(th) for th in ths: th.join() s = json.dumps(self.data, ensure_ascii=False, indent=4) with open('top_th1.json', 'w', encoding='utf-8') as f: f.write(s) print('Data crawling is finished.')if __name__ == '__main__': Spider().run()复制代码
这里的整体思路和上一篇文章没有什么区别。分配两个队列,一个存储二级页面的URL,一个存储抓取到的数据。一级页面单独开一个线程,将二级页面URL不断填入队列中。解析二级页面URL时开启多个线程提高抓取速度。
除此之外,还需要说明一个地方
我们上一篇文章中,因为URL队列是事先产生的,而不是生产和消耗URL程序同时进行,因此队列一旦为空即结束爬虫。而这里的一级页面和二级页面的解析是同时进行的,也就是说二级页面URL是边生产边消耗的,这时我们就要保证
- 所有页面解析结束可以退出所有线程(如果只是单纯
while True
,URL列表为空时,消耗线程就会永远等下去) - 不会因为二级页面URL消耗太快而使队列提前为空,提早退出爬虫
对于第二点,这里定义了self.first_running
,它如果是True
,则表示一级页面还没运行完成。此时即使二级页面的URL队列已经空了,也要继续等待一级页面解析后产生新的二级页面URL。
另外,由于这次URL队列是典型的,因此如果不想自己实现Condition锁的话,就用Queue来代替list。
读者也可以试着更改self.thread_num
看爬虫速度有什么改变。
欢迎关注我的知乎专栏
专栏主页:
专栏目录:
版本说明: