python3多进程爬虫_爬虫系列(三)——多进程爬虫

承接上文,实现多任务的方法一般有三种,今日来实现其中的一种:多进程爬虫。而剩余的多线程并发和协程暂时没时间搞了,一方面是要先学习下Python的yield和send如何使用,其次要准备毕设的中期答辩和被老师安排了去读源代码,日后会补上的。但仅仅是多进程,就将IO密集的任务从1153秒提升到了105秒,且极高的提升了资源利用率。

还有一点,本文的代码可能只适用于Next主题,但方法思路是通解,只要改改参数适应你的主题,代码同样能用。结果展示:本站热门

爬虫

如果对爬虫不太了解,可以参考我之前的几篇文章。如果你计算机功底还不错,那么实现爬虫入门,完成简单的爬虫任务还是可以的:

这次的爬取任务很简单,之前看到了别人博客有『热榜』这个东西,很是眼馋,查询了一下发现实现热榜这个东西得经过很多的配置,但是我懒不想配置。于是准备自己动手爬虫,爬取每篇博客的访问量,以此作为博客每排文章的热度,说干就干,开工。

爬虫思路本次爬虫需要request、BeautiulSoup和selenium联合使用,只有其中的一方完不成任务。

因为每篇博客的访问量并不在外部显示,必须点击到文章内部才难加载,所以,这次爬虫必须使用selenium控制浏览器模拟点击,之后进入对应网页,以此来获取不蒜子统计的浏览数量。

首先打开网页的根域名,按F12发现每篇文章的链接的类是post-title-link,以此来获取第一篇文章的名称,点击,进入第一篇博客。部分代码如下所示:

# 寻找第一篇文章

first_page = body.find('a', {'class', 'post-title-link'})

# 记录第一篇文章的标题

first = ""

for i in first_page:

first = i

# 进入第一篇文章

driver.find_element_by_xpath("//a[contains(text(),'{}')]".format(first)).click()

本来想的是,进入网页后,使用requests库直接请求,然后解析出不蒜子统计那个标签的数量。但是,requests网页后发现不蒜子标签下数量根本不显示。意思是,仅仅依靠requests和beautifulsoup的爬虫是爬取不到不蒜子统计的。

所以,还得靠selenium。再次按一下F12,发现不蒜子统计的id是busuanzi_value_page_pv。利用selenium的id定位法,定位到不蒜子统计元素,然后输出定位元素里的文本。部分代码如下所示:

view = driver.find_element_by_id("busuanzi_value_page_pv")

# num 就是每篇文章的访问量

num = int(view.text)

而后,在每篇博客的末尾,都有指向下一篇博客的链接,点击既能进入下一篇博客。如下图所示:

而selenium定位到链接的文字又很费劲,不如用selenium获取当前网页的URL,然后使用requests和beautifulsoup库跟去获取的URL,进一步解析出指向下一篇博客链接的文字。而后把解析出的文字传给selenium的链接定位法,让selenium按照文字去点击链接,进入下一篇博客。如此循环往复,直到最后一篇博客。

因某些博客的图片数量过多,导致加载过慢,如果5秒内加载不完,则直接进入下一篇文章。中间为防止爬虫过猛被屏蔽,其中使用了一些延时函数放缓进度。

最终的结果要把文章的名称、文章的网址和文章的访问量都记录下来。考虑使用字典这种结构,将文章名称和网址作为key,访问量作为value。

将字典保存为json文件,而后将json文件中的结果稍微清洗一下,做成markdown文件,放到博客里,热榜就做成了。

最后,对爬虫任务的整体时间进行记录,对比单进程和多进程的效率。

单进程import requests

from bs4 import BeautifulSoup

home_url = "https://muyuuuu.github.io/"

# 申请当前页面

r = requests.get(home_url)

soup = BeautifulSoup(r.text, "lxml")

body = soup.body

# 使用selenium点开每个页面,统计访问人数和评论数

from selenium import webdriver

driver = webdriver.Chrome(executable_path='/home/lanling/chromedriver_linux64/chromedriver')

import time

# 记录文章的数量

article = {}

# 先进入第一页

# 打开网页

driver.get(home_url)

# 寻找第一篇文章

first_page = body.find('a', {'class', 'post-title-link'})

# 记录第一篇文章的标题

first = ""

for i in first_page:

first = i

# 进入第一篇文章

driver.find_element_by_xpath("//a[contains(text(),'{}')]".format(first)).click()

time.sleep(3)

# 记录浏览数

dr = driver.find_element_by_id("busuanzi_value_page_pv")

# 标题名和域名作为字典的 key

string = "[" + first + "]" + "(" + driver.current_url + ")"

article[string] = int(dr.text)

print(article)

# 而后点击 每篇博客末尾的指向下一篇文章的链接 直到遍历完所有文章

# 记录开始时间

start = time.time()

# 捕获当前的 url

url = driver.current_url

data = requests.get(url)

data = BeautifulSoup(data.text, "lxml")

# 解析当前页面,获取下一页的按钮

next = data.find('div', {'class':'post-nav-next post-nav-item'})

try:

while next:

time.sleep(1)

# 点击下一页的链接 进入 下一个网页

driver.find_element_by_link_text(next.text.strip()).click()

time.sleep(5)

# 查看浏览数,图片较多加载缓慢,5秒内加载不出来跳过

try:

view = driver.find_element_by_id("busuanzi_value_page_pv")

except:

pass

# 获取当前页面的url

url = driver.current_url

time.sleep(1)

data = requests.get(url)

# 解析当前url

data = BeautifulSoup(data.text, "lxml")

# 当前文章的标题和域名传入字典

string = "[" + data.title.text + "]" + "(" + driver.current_url + ")"

# 如果没有加载到浏览数 就给一个负数

if view.text == "":

article[string] = -2

else:

article[string] = int(view.text)

print(string, int(view.text))

time.sleep(1)

# 寻找下一篇博客的链接名,然后点击,直到最后一篇文章。

next = data.find('div', {'class':'post-nav-next post-nav-item'})

except:

# 记录结束时间

end = time.time()

print(end - start)

driver.quit()

# 将博客按访问数排序

article_save = sorted(article.items(), key = lambda item:item[1])

import json

# encoding='utf-8',用于确保写入中文不乱码

with open('data.json','w',encoding='utf-8') as f_obj:

# ensure_ascii=False,用于确保写入json的中文不发生乱码

json.dump(article_save,f_obj,ensure_ascii=False, indent=4)

这个单进程的任务总共耗时1153秒左右(毕竟网络时延说不准),资源利用率很少,如下所示:

多进程

多进程仅需在单进程的代码上做一点改动即可:(如果你不懂多进程,请参考我的上一篇文章,如何理解多进程)

首先统计一下博客有几页,就创建几个进程。比如,我的博客目前有14页,就创建14个进程:

进程扔到一个进程池里,等待所有进程执行完毕即可。

多进程之间不共享变量,所以字典的创建方法需要更改。

同样,防止爬虫过猛,中间同样适用延时函数放缓进度。

记录多进程的耗时,与单进程对比。代码如下所示:

import requests

from bs4 import BeautifulSoup

home_url = "https://muyuuuu.github.io/"

r = requests.get(home_url)

soup = BeautifulSoup(r.text, "lxml")

# 有几个页面就创建几个进程

body = soup.body

page_number = body.find('nav', {'class' : 'pagination'})

a = page_number.find_all('a')

max_page_number = 0

for i in a:

for j in i:

if str(j).isdigit():

if int(j) > max_page_number:

max_page_number = int(j)

print(max_page_number)

process_num = max_page_number

# 扔进进程池统一管理 并计时

from multiprocessing import Pool

import time, random

from selenium import webdriver

import multiprocessing

# 进程的字典

article = multiprocessing.Manager().dict()

# 记录每个进程爬几次,就是一个页面有几篇文章,我懒的爬取了,手动设置下

per_page = 7

# 每个进程的任务

def respite_task(url_num):

num = 1

sub_url = ""

if url_num == 0:

sub_url = "https://muyuuuu.github.io/"

else:

sub_url = "https://muyuuuu.github.io/page/" + str(url_num) + "/"

# 每个进程启动一个 driver

print("here")

driver = webdriver.Chrome(executable_path='/home/lanling/chromedriver_linux64/chromedriver')

driver.get(sub_url)

time.sleep(random.randint(2, 6))

r = requests.get(sub_url)

soup = BeautifulSoup(r.text, "lxml")

body = soup.body

# 寻找第一篇文章

first_page = body.find('a', {'class', 'post-title-link'})

# 记录第一篇文章的标题

first = ""

for i in first_page:

first = i

# 进入第一篇文章

driver.find_element_by_xpath("//a[contains(text(),'{}')]".format(first)).click()

time.sleep(5)

# 记录浏览数

try:

view = driver.find_element_by_id("busuanzi_value_page_pv")

except:

pass

# 标题名和域名作为字典的 key

string = "[" + first + "]" + "(" + driver.current_url + ")"

# 如果没有加载到浏览数 就给一个负数

if view.text == "":

article[string] = -2

else:

article[string] = int(view.text)

print(string, int(view.text))

# 捕获当前的 url (已经进入了新页面)

url = driver.current_url

data = requests.get(url)

data = BeautifulSoup(data.text, "lxml")

# 解析当前页面,获取下一页的按钮

next = data.find('div', {'class':'post-nav-next post-nav-item'})

try:

while next and num < per_page:

num += 1

time.sleep(1)

# 点击下一页的链接 进入 下一个网页

driver.find_element_by_link_text(next.text.strip()).click()

time.sleep(5)

# 查看浏览数,图片较多加载缓慢,5秒内加载不出来跳过

try:

view = driver.find_element_by_id("busuanzi_value_page_pv")

except:

pass

# 获取当前页面的url

url = driver.current_url

time.sleep(1)

data = requests.get(url)

# 解析当前url

data = BeautifulSoup(data.text, "lxml")

# 当前文章的标题和域名传入字典

string = "[" + data.title.text + "]" + "(" + driver.current_url + ")"

# 如果没有加载到浏览数 就给一个负数

if view.text == "":

article[string] = -2

else:

article[string] = int(view.text)

print(string, int(view.text))

time.sleep(1)

# 寻找下一篇博客的链接名,然后点击,直到最后一篇文章。

next = data.find('div', {'class':'post-nav-next post-nav-item'})

except:

pass

finally:

driver.quit()

p = Pool(process_num)

for i in range (process_num + 1):

p.apply_async(respite_task, args=(i,))

print('waitting for all subprocess done')

start = time.time()

p.close()

p.join()

end = time.time()

print('All subprocesses done costs {} seconds'.format(end - start))

# 升序排列并保存

article_save = sorted(article.items(), key = lambda item:item[1])

import json

# encoding='utf-8',用于确保写入中文不乱码

with open('data_multiprocess.json','w',encoding='utf-8') as f_obj:

# ensure_ascii=False,用于确保写入json的中文不发生乱码

json.dump(article_save,f_obj,ensure_ascii=False, indent=4)

最终的结果和单线程的结果保持一致,但仅仅用了105秒就执行完毕,足足提升了10倍。因为每个进程都有一个主线程,将主线程分配给多个核,所以导致了资源利用率比单线程要高的多:

结语

这好像不是传说中的并行计算,毕竟不是计算密集的任务,但这个几个进程的任务确实在并行执行,『知识就是力量』还是有道理的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值