java爬取某站整部小说(多线程版)
本帖最后由 甘霖之霜 于 2023-4-15 11:53 编辑前言
前几天发一个帖子https://www.52pojie.cn/thread-1773549-1-1.html
各路大佬纷纷提出优化方案
其中@Ashtareve 给提出了并行流解决方案我实装一下 速度也是相当滴快(相比之前蜗牛速度)
决定把源码发出来
程序源码还是简单介绍一下把思路
获取目录代码
先获取目录 根据目录到具体的页面爬取对应章节
参数说明 root 网站根路径(不是起始路径) next:下一页 dir:目录
private static void getDir(String root,String next,List<String> dir) throws Exception {
Document document = Jsoup.connect(next).get();
Elements elements = document.select("a");
List<String> list = elements.eachAttr("href");
list.remove(0);
if (elements.last().text().equals("下一页")){
String nextPage = list.get(list.size() - 1);
nextPage = root +nextPage.substring(nextPage.lastIndexOf("/") + 1);
list.remove(list.size() - 1);
if (elements.get(elements.size() - 2).text().equals("上一页")){
list.remove(list.size() - 1);
}
dir.addAll(list);
getDir(root,nextPage,dir);
return;
}
if ((elements.last().text().equals("上一页"))){
list.remove(list.size() - 1);
}
dir.addAll(list);
}
单章节获取
参数说明 url该章节地址
private static String getDetail(String url){
try {
Document document = Jsoup.connect(url).get();
String title = document.select("h1").text() + "\n";
System.out.println(title);
Elements content = document.select("div");
String text = content.toString();
int i = text.indexOf("&");
if (i != -1){
text = text.substring(i);
}
text = text.replaceAll(" ","").replaceAll("<br><br>","").replaceAll("</div>","");
return title + text;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
使用stream并行流获取全部章节
此为 @Ashtareve 大佬提出的解决方法 还是很有必要说明一下这种简单高效的处理方式深得我心
参数说明 dir 刚才获取的目录 root 同上 writer输出流用于保存本地
private static void getContent(List<String> dir,String root, Writer writer) throws Exception {
List<String> list = dir.stream().parallel().map(url -> getDetail(root + url)).toList();
System.out.println("正在写入文件...");
IOUtils.writeLines(list,"\n",writer);
writer.close();
IOUtils.close();
}
程序依赖
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
整体代码
public class Demo {
public static void main(String[] args) throws Exception {
System.out.println("软件来自:吾爱破解 甘霖之霜");
Scanner input = new Scanner(System.in);
System.out.println("书籍搜索地址:https://www.bbiquge.net/");
System.out.print("请输入书籍编号:");
String s = input.next();
long begin = System.currentTimeMillis();
String url = String.format("https://www.bbiquge.net/book/%s/",s);
String fileName = Jsoup.connect(url).get().select("h1").text();
fileName = fileName.replace("/","") + ".txt";
File file = new File("D:\\" + fileName);
Writer writer =new FileWriter(file,true);
List<String> dir = new ArrayList<>();
System.out.println("正在获取目录中...");
getDir(url,url,dir);
System.out.println("正在获取章节内容...");
getContent(dir,url,writer);
System.out.println("下载完成,文件已保存至 D:\\" + fileName);
System.out.printf("花费时间%s秒",(System.currentTimeMillis() - begin) / 1000);
input.next();
}
private static void getDir(String root,String next,List<String> dir) throws Exception {
Document document = Jsoup.connect(next).get();
Elements elements = document.select("a");
List<String> list = elements.eachAttr("href");
list.remove(0);
if (elements.last().text().equals("下一页")){
String nextPage = list.get(list.size() - 1);
nextPage = root +nextPage.substring(nextPage.lastIndexOf("/") + 1);
list.remove(list.size() - 1);
if (elements.get(elements.size() - 2).text().equals("上一页")){
list.remove(list.size() - 1);
}
dir.addAll(list);
getDir(root,nextPage,dir);
return;
}
if ((elements.last().text().equals("上一页"))){
list.remove(list.size() - 1);
}
dir.addAll(list);
}
private static void getContent(List<String> dir,String root, Writer writer) throws Exception {
List<String> list = dir.stream().parallel().map(url -> getDetail(root + url)).toList();
System.out.println("正在写入文件...");
IOUtils.writeLines(list,"\n",writer);
writer.close();
IOUtils.close();
}
private static String getDetail(String url){
try {
Document document = Jsoup.connect(url).get();
String title = document.select("h1").text() + "\n";
System.out.println(title);
Elements content = document.select("div");
String text = content.toString();
int i = text.indexOf("&");
if (i != -1){
text = text.substring(i);
}
text = text.replaceAll(" ","").replaceAll("<br><br>","").replaceAll("</div>","");
return title + text;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
使用方式
https://www.bbiquge.net去这个网站搜索点进详情 看地址栏
举个栗子
https://www.bbiquge.net/book/59265/ 其中59265 是书籍编号 输入即可
https://file.ffxs.cn/view.php/9f0cf3e563a32b2a5692adba219f9671.png
结果
https://file.ffxs.cn/view.php/280f988e106963490178f1cdb1cc063f.png
一千多章 速度我个人还挺满意(可能是之前的蜗牛速度)
下载地址:https://wwi.lanzoup.com/imQLI0t1amra 本帖最后由 话痨司机啊 于 2023-4-16 17:31 编辑
我也写了一个,不知道哪个速度快一些~
我还打包了一份:
https://www.123pan.com/s/E3kbVv-V8RBH.html提取码:UlXn
import requests
from lxml import etree
from concurrent.futures import ThreadPoolExecutor, as_completed
import os
from tqdm import tqdm
from queue import PriorityQueue
import time
import re
header = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.39"
}
QUEUE = PriorityQueue()
every_chapters_list: list = []
nove_title: str = ''
def get_next_page(next_url):
"""获取下一页"""
global nove_title
response = requests.get(next_url, headers=header)
html = etree.HTML(response.text)
# href="javascript:"
next_page_url = html.xpath("//a/@href")
_nove_title = html.xpath("//h1/text()")
if nove_title:
nove_title = _nove_title
every_chapters = html.xpath("//dl[@class='zjlist']//dd/a/@href")
if next_page_url:
if "javascript:" not in next_page_url:
if "index" in next_url:
every_chapters = + "/" + e for e in every_chapters]
else:
every_chapters =
every_chapters_list.extend(every_chapters)
get_next_page("https://www.bbiquge.net" + next_page_url)
else:
print('章节获取完毕,准备开始下载小说内容')
def every_chapter(index, chapter_url):
"""请求单个章节内容"""
response = requests.get(chapter_url, headers=header)
html = etree.HTML(response.text)
# title
title: list = html.xpath("//h1/text()")
# content
content: list = html.xpath('//div[@id="content"]//text()')
QUEUE.put((index, title + ['\n'] + content + ['\n']))
def get_novel_chapters():
"""多线程下载"""
with ThreadPoolExecutor(max_workers=os.cpu_count() + 4) as executor,tqdm(total=len(every_chapters_list)) as pbar:
futures = [executor.submit(every_chapter, index, url)
for index, url in enumerate(every_chapters_list)]
for future in as_completed(futures):
if future.done():
pbar.update(1)
def filter_content(strings:str):
return strings.replace(r"笔趣阁 www\.bbiquge\.net,最快更新宇宙职业选手最新章节!",'').replace(r'\*\*\*\*\*\*','')
def save_chapters(filename):
with open(filename + '.txt','a',encoding='utf8') as f:
while True:
if QUEUE.empty():
break
novel_chapter = QUEUE.get()
if novel_chapter:
f.write(filter_content(''.join(novel_chapter)))
if __name__ == "__main__":
regex = r"^https:\/\/www.bbiquge.net\/book/\d+\/$"
download_url = input(
'请输入要下载的小说地址(ex:\"https://www.bbiquge.net/book/133312/\"):')
if isinstance(download_url, str) and re.match(regex,download_url):
download_url = download_url.strip()
print('正在获取章节,请稍等...')
get_next_page(download_url)
get_novel_chapters()
save_chapters(nove_title)
else:
raise ValueError("输入有误")
甘霖之霜 发表于 2023-4-15 11:33
交互这块确实没怎么去优化还是只分享源码得了我
移动到D盘目录后,可以下载了。
很快,很强大!{:1_921:} xhygf 发表于 2023-4-15 11:40
但是软件自己直接退出了!
估计是jre的问题 自己构建吧 源码在 打包这块确实没怎么研究过 我把1万多章的校花贴身高手都下载下来了 多线程和单线程的优缺点是什么? xiliangjingmach 发表于 2023-4-15 10:43
多线程和单线程的优缺点是什么?
之前下载1千章大概需要半个小时还多 现在100多秒搞定 如何获悉书籍编号 水上凌波 发表于 2023-4-15 11:07
如何获悉书籍编号
看下帖子下半部分 有说明的 无故退出,用不了 水上凌波 发表于 2023-4-15 11:12
无故退出,用不了
输入编号不存在 会直接退出 主要分享源码,打包这块并不经常干 我可能是不太会用。
能看到它找到了目录,但是最后文件是0K newsafestone 发表于 2023-4-15 11:29
我可能是不太会用。
能看到它找到了目录,但是最后文件是0K
交互这块确实没怎么去优化还是只分享源码得了我 目录都是显示出来,但是下载后text文件里没有内容,是什么情况?