# coding=utf-8
import gevent
from gevent import monkey
# monkey.patch_all()
gevent.monkey.patch_all(thread=False, socket=False, select=False)
# 协程gevent库和多进程,进程池冲突,需要关闭thread
# 如不关闭, 代码会卡至创建进程池处.
import requests
import time
# import sys
from requests.adapters import HTTPAdapter
from bs4 import BeautifulSoup
# import multiprocessing
from multiprocessing import Pool
# sys.setrecursionlimit(10000)
rs = requests.Session()
rs.mount('http://', HTTPAdapter(max_retries=30))
rs.mount('https://', HTTPAdapter(max_retries=30))
# 设置最高重连次数
# import threading
# 测试之后决定放弃多线程使用
# 在爬取数据上.
# 相比较多进程下多线程
# 多进程下协程更具有性能优势.
# class MyThread(threading.Thread):
# """重写多线程,使其能够返回值"""
# def __init__(self, target=None, args=()):
# super(MyThread, self).__init__()
# self.func = target
# self.args = args
#
# def run(self):
# self.result = self.func(*self.args)
#
# def get_result(self):
# try:
# return self.result # 如果子线程不使用join方法,此处可能会报没有self.result的错误
# except Exception:
# return None
# lock = threading.Lock()
# 获取小说内容
def extraction_chapter(id, chapter_url, threads_content):
"""获取小说内容"""
res = rs.get(chapter_url, timeout=(5, 7))
# print(result)
# res.encoding = "gbk"
# print (res)
soup = BeautifulSoup(res.text, 'lxml')
# print(soup)
# title = soup.select('div.txtbox > h1')[].text
title = soup.select('#txtbox > h1')
content = soup.select('#content')
# con = title + content
title_str = str(title[0])
content_str = str(content[0])
# print(content_str)
title_re = title_str.replace('<h1>', '')
title_re = title_re.replace('</h1>', '
')
content_re = content_str.replace('<div id="content">', '')
content_re = content_re.replace('<p>', '
')
content_re = content_re.replace('</p>', '')
content_re = content_re.replace('</div>', '')
make_sign = "
_____(ฅ>ω<*ฅ)喵呜~~~_____
" # 小mark
con = title_re + content_re + make_sign
threads_content[id] = con
# 此处通过字典输入内容
# 获取小说每章网址(已分进程)
def extraction(novel_url, ):
# print("+")
res = rs.get(novel_url, timeout=(3, 5))
# 输入小说总页面
# 获取元素
soup = BeautifulSoup(res.text, 'lxml')
start_time = time.time()
# 寻找书名
novel_title = soup.select('#bookinfo-right>h1')
novel_title = str(novel_title[0])
novel_title = novel_title.replace('<h1>', '')
novel_title = novel_title.replace('</h1>', '')
print("开始: >>>"+novel_title+"<<< ")
chapter_all = soup.select('#list>ul>li>a')
# 获取章节所在元素,a标签
# chapter = str(chapter[0].attrs["href"])
# 获取a标签href属性
# print(type(chapter_all))
file_name = novel_title + '.txt'
with open(file_name, 'w', encoding='utf-8') as f:
f.write('')
# content_con = ""
id = 0
g_list = []
threads_content = {}
# 遍历拼接每章网址
for chapter in chapter_all:
chapter = str(chapter.attrs["href"])
# 获取a标签href属性
chapter_url = novel_url + chapter
# 完成拼接
# print("协程创建+")
# charpter_con = extraction_chapter(chapter_url)
# 调用子方法, 萃取每章内容.
# 使用协程提高效率
# charpter_con = gevent.spawn(extraction_chapter, chapter_url)
# charpter_con.join()
g = gevent.spawn(extraction_chapter, id, chapter_url, threads_content)
id += 1
g_list.append(g)
# 等待所有协程任务完成
gevent.joinall(g_list)
# 遍历所有线程,等待所有线程都完成任务
# for t in threads:
# t.join()
# print(content_con)
# 遍历线程字典, 导入内容
# i = 0
# value = ""
# while i <= len(threads_content):
# value = value + threads_content[i]
# i += 1
# con_content = ""
threads_content_key = sorted(threads_content.keys())
# 字典排序, 按照key值从小到大排列
for i in threads_content_key:
# lock.acquire()
with open(file_name, 'a', encoding='utf-8') as f:
f.write(threads_content[i])
# lock.release()
# con_content += threads_content[i]
# 存储为字符串, 遍历完之后一次写入.[测试时间204]
# threads_content.clear()
# with open(file_name, 'a', encoding='utf-8') as f:
# f.write(con_content)
#
# del con_content
# 清除
end_time = time.time()
elapsed = str( float('%.2f' % (end_time - start_time)) )
with open('console.log', 'a', encoding='utf-8') as f:
f.write("Spend:["+ elapsed + "s] <<"+novel_title+">>
")
print("Spend:["+ elapsed + "s] <<"+novel_title+">>")
# 完本页面网址
def end_book(end_url):
res = rs.get(end_url, timeout=(3, 5)) # 连接超时和读取超时时间设置
# 输入小说总页面
# 获取元素
soup = BeautifulSoup(res.text, 'lxml')
# 寻找书籍a元素
novel_name = soup.select('.bookimg>a')
# print("准备创建进程")
# 定义进程池, 默认为cpu核数
# print("创建进程池")
# 默认进程数量为核心数量
po = Pool(8)
# 使用八进程
# 使用协程后能效得到控制, 可根据总爬取数量进行更改.
# ><><><测试><><><><
# 处理器:i5,3230M 四核, 内存8G
# 爬取内容为同页,21本,每本约300章,30.9MB. 网络有浮动, 以下测试数据仅能作为参考
# >>效率对比<<
# 4进程-协程,91s,118s,125s,131s,109s,100s <112.3> 四核CPU占用均约: 32% 内存最高占用:71.5%
# 8进程-协程,74s,91s,89s,86s,89s,67s,80s,65s <80.12> 四核CPU占用均约: 45% 内存最高占用:77.9%
# 12进程-协程,89s,96s,73s,81s,82s,78s,74s,69s <80.25> 四核CPU占用均约: 67% 内存最高占用:85.7%
# <根据本数决定进程数>
# 21进程-协程,82s,96s,89s,90s,85s <88.4> 四核CPU占用均约: 72% 内存最高占用:93.7%
# <<>><><><>
"""
总结:
计算密集型项目, 就只需使用多进程(核心数),能够达到最大效率,可跑满每颗核心.(核心数+1)可避免因为内存页缺失导致的计算资源浪费,可能造成一拖多现象,应根据具体情况调整.
I/O 密集型项目, 则使用多进程,加线程或协程.(大部分爬虫项目,协程比多线程更有效率.)
在I/O密集型任务当中,多进程+协程的解决方案,应该适当变动进程数量.
决定因素有:
1.硬件性能.
CPU: CPU尚未跑满,则尚有提升空间,可适当增加进程(N*核心数,N<=3).
内存: 一旦写满未能及时释放进程占用,则崩溃, 应减少进程.
(硬盘写入门槛在小项目中很难触碰. 尤其是爬虫类,在使用协程时可不考虑)
2.网络.
自身带宽: 爬虫项目中, 带宽上限应为最终门槛.获取数据达到带宽上限, 代码可不必再进行优化. 遗憾的是此项目中, 抓取效率最高为800+Kb/s,远远未达到目标.
网页载入: 爬虫项目中最重要的限制, 页面的载入速度越快,获取数据越快,则进程应越少. 页面载入越慢, 则进程应越多才可提升效率,减少一拖多成本.
3.项目总量.
项目体量过大的时候, 应当仔细计算I/O时间与计算时间
公式应为: (IO时间+计算时间)/(计算时间+进程数*调度消耗)
***** 此公式另贴细表 *****
项目体量不大的时候, 就根据具体的项目数量决定进程数
此项目中, 因为分页, 每页的21本书进行多进程操作.所以进行了一下这种非常规测试.
虽然此处效率并不是很理想, 但是这种因地制宜进程数必定有可取之处.
"""
# print("准备创建进程+")
for name in novel_name:
# 获取每个元素的网址
# print("进程创建")
novel_url = name.attrs["href"]
# print(novel_url)
# extraction(novel_url)
# 把 网址传入方法.
# 进程池方式进行,把进程放入进程池
# p = multiprocessing.Process(target=extraction, args=(novel_url,))
po.apply_async(extraction, (novel_url,))
# p.start()
# p_list.append(p)
po.close()
po.join()
# 为避免抓取中断, 进程池设置, 本页数据抓取完毕之后再抓取下一页. 牺牲了一些性能, 可酌情更改
def book(index_url, start, end):
num = start
while num <= end:
start_time = time.time()
index = '/index_' + str(num) + '.html'
if num == 1:
index = "/"
# 全本书索引页面
index_con = index_url + index
print(index_con) # 输出网址
# 调用全本方法, 并传入参数
end_book(index_con)
end_time = time.time()
# 传入耗时参数
elapsed = str(float('%.2f' % (end_time - start_time)))
localtime_end = time.asctime(time.localtime(time.time()))
with open('console.log', 'a', encoding='utf-8') as f:
f.write(
'
' + '*' * 50 + '
'+ index +" "+ '消耗时间= ' + elapsed + "
" + localtime_end + "
"+ '*' * 50+'
')
num += 1
if __name__ == '__main__':
# 输入网址
url = "https://www.xxxxx.com/quanben" # 此处输入小说总网址
page_start = 1 # 开始页数
page_end = 96 # 结束页数
# 开始时间
start_time = time.time()
localtime = time.asctime(time.localtime(time.time()))
with open('console.log', 'w', encoding='utf-8') as f:
f.write('<=====Start=====>
' + localtime + '
'+'-'*50+'
')
book(url, page_start, page_end)
# 结束时间
end_time = time.time()
# 耗时
elapsed = str( float('%.2f' % (end_time - start_time)) )
localtime_end = time.asctime(time.localtime(time.time()))
with open('console.log', 'a', encoding='utf-8') as f:
f.write('
'+'-'*50+'
'+'消耗时间=====' + elapsed + " " + "
"+ localtime_end+"
<=====Start=====>")
print('消耗时间:' + elapsed)