一次python爬取 笔趣看小说网 的记录
相关技术
python, lxml, requests, 消息队列,多线程(开了一百个线程,小说下载速度超快)
项目结构
自己构建了一个IP代理池,将不干净的 (混乱IP.txt) 文件中的IP清洗过后,写入到(可用的IP.txt)文件中
爬虫有一定的时效性,刚写好的爬虫,说不定过一天就失效了,所以要看就看最新的吧,笔趣看小说网 会封IP的,单个IP去爬取
下载速度会越来越慢,所以必须提前准备好IP代理。
网站首页: https://www.biqukan.com
注意:该网站采用 gbk 编码,页面中充斥着大量 \0a0, \r 等字符, \r 会让光标回到行首,从而导致后面的内容覆盖前面的内容
项目的 gitee 地址: https://gitee.com/o1uncle/spyder_biqukan
主要代码如下:
# -*- coding:utf-8 -*-
"""
开发者:o1uncle
日 期:2020 年 07 月 12 日
description:---*- 爬取笔趣看小说网 -*---
多线程,IP 代理
思路:
1:获取小说的所有章节链接
2:将章节 (章节号 + 链接) 全部 put 到队列 urls_queue 中,章节号用于下载完成后的排序
3:重写线程,构造run(), 从 urls_queue 中 get, 处理好之后将(章节号 + 章节内容) put 到 wait_queue
4:wait_queue 队列中的数据,放到一个列表中,排序
5:排序之后将列表写到文件中
不足:这里一次只能下载固定链接的一本小说
"""
import requests
from lxml import etree
import random
from queue import Queue
from threading import Thread
headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36"
}
ip_pool = [] # IP 代理池
current_progress = 0
novel_name = 'none' # 小说的名字
# 构造 IP 代理池
def creat_ip_pool() -> list:
global ip_pool
with open('../3-2-available-ip.txt', 'rt') as f:
while True:
ip = f.readline()
if ip == '':
# return ip_pool
break
ip_pool.append(ip.strip('\n'))
def all_urls_enter_queue(urls_queue: Queue, aurl='/1_1680/'): # 下载一本小说
global headers
global novel_name
url = 'https://www.biqukan.com'
req = requests.get(url + aurl, headers)
req.encoding = 'gbk'
e = etree.HTML(req.text)
# 得到所有的章节链接
sk = e.xpath('//h2/text()')
novel_name = sk[0]
result = e.xpath('//dd[position()>12]/a/@href')
# ['/1_1680/16813635.html', '/1_1680/16813636.html',
# 将(章节号+链接)全部压入队列
for i in range(len(result)):
urls_queue.put((i, url + result[i])) # 'https://www.biqukan.com/1_1680/16813636.html'
# for i in range(100): # 下载一部分链接,用于调试,以免对服务端产生太大压力
# urls_queue.put((i, url + result[i])) # 'https://www.biqukan.com/1_1680/16813636.html'
class DownloadNovel(Thread):
def __init__(self, urls_queue: Queue, wait_queue: Queue):
self.urls_queue = urls_queue
self.wait_queue = wait_queue
Thread.__init__(self)
def download_one_chapter(self, url):
# 发起网络请求
ip = random.choice(ip_pool)
proxies = {'http': 'HTTP://{}'.format(ip)}
while True:
try:
req = requests.get(url, headers=headers, proxies=proxies, timeout=5)
# 下载正常
if req.status_code == 200:
break
except:
ip = random.choice(ip_pool)
proxies = {'http': 'HTTP://{}'.format(ip)}
print('章节下载超时,更换代理,重新下载。')
# req.encoding = 'utf8' # 错误的方式,控制台依然显示乱码
# 乱码解决方式一:
# req.encoding = 'gbk'
# 乱码解决方式二:
try:
html = req.content.decode('gbk')
except:
print('gbk 解码出错')
e = etree.HTML(html)
# 拿到章节标题
title = e.xpath('string(//h1)')
# 拿到章节内容
content = e.xpath('//div[@id="content"]/text()')
text = ''
for i in range(len(content)):
text += content[i].replace('\xa0', '').replace('\r', '\n')
# 章节名称和正文部分分行显示
return '\n' + title + '\n\n' + text
def run(self):
global current_progress
while not self.urls_queue.empty():
num, url = self.urls_queue.get()
text = self.download_one_chapter(url)
current_progress += 1
print('当前进度:', current_progress)
'3:重写线程,构造run(), 从 urls_queue 中 get, 处理好之后将(章节号 + 章节内容) put 到 wait_queue'
self.wait_queue.put((num, text))
pass
def main(aurl: str) -> None: # 下载一本小说
urls_queue = Queue()
wait_queue = Queue()
creat_ip_pool() # 构造了IP代理池
# print(random.choice(ip_pool))
# 所有需要下载的url 进入队列
all_urls_enter_queue(urls_queue=urls_queue, aurl=aurl)
dt = []
for i in range(100):
t2 = DownloadNovel(urls_queue, wait_queue)
t2.start()
dt.append(t2)
for i in range(100):
dt[i].join()
print('小说的章节遍历完毕')
# 此时 wait_queue 中的章节为乱序存放
all_text_lt = []
while not wait_queue.empty():
all_text_lt.append(wait_queue.get())
# 排序
all_text_lt = sorted(all_text_lt, key=lambda x: x[0]) # [(0, '...'), (1, '...'), ...]
# 保存小说,以列表的形式写入到文件中
text = ''
# 愣是 忘记些 range 了
for i in range(len(all_text_lt)):
text += str(all_text_lt[i][1])
with open('{}.txt'.format(novel_name), 'w') as stream:
stream.write(text)
print('{} 下载完成'.format(novel_name))
if __name__ == "__main__":
url = '/1_1680'
main(aurl=url)