Java写爬虫,你试过嘛?1000本小说100行代码搞定
【摘要】 我徒然学会了抗拒热闹,却还来不及透悟真正的冷清。——张大春 《四喜忧国》
入手二手Kindle Voyage一部, 准备下几本书,知乎找到一个叫好读的网站,发现好多好书,繁体竖版,嘻嘻,下了几次,读着感觉棒棒的,所以,想把整个网站的书都爬下来。哈哈…, 而且分析网站,表格布局,SEO友好。
- 初步定的方案用 python,因为IO读写方便,结合
xpath
,后来搭了环境,发现好多都忘记了,需要复习,所有最后决定用java,结合jsoup,htmlUtil
等。 - 关于 jsoup ,可以看我的博客:Jsoup学习文档
- 捣鼓了一晚上,折腾到凌晨3、4点多,终于爬了下来。原本想一个页面下载小说的多个类型,后来发现做不到,一段代码并行跑的。
- 爬取小说的网站
设计到技术点:
- 需要模拟下载按钮的点击,还有之后弹出的确认框的按钮点击。这里的思路是调用两次按钮点击事件对应方法,第一次click返回page,获取按钮Element在调用一次返回的page直接输出为IO,
- 按钮的多次点击之间,页面会通过js动态生成Element。如果两次点击事件串行触发,可能需要的Element数据没有加载出来,获取不到第二次的按钮元素。报NullPointException。这个处理是让线程sleep了一秒。确保js加载的Element可以加载出来。
- 当前代码同一个页面不支持多次按钮点击下载,如果因为在一次下载完无法获取到当前页面了,所以不能并行操作,解决办法现在还没想到,小伙伴可以留言idea。
- 剩下的需要注意一些版本依赖问题。
- 默认的处理异常逻辑为,当前小说下载出现异常会直接跳过。
代码没有处理,需要优化的可以自行处理下
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.liruilon</groupId>
<artifactId>spider</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- simulate web browser -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
<!-- parse DOM -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<!-- parse javascript -->
<dependency>
<groupId>org.mozilla</groupId>
<artifactId>rhino</artifactId>
<version>1.7.10</version>
</dependency>
<!-- simulate client action -->
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.33</version>
</dependency>
<!-- upgrade junit to junit4 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12<!-- default is v3.8.1 --></version>
<scope>test</scope>
</dependency>
<!-- log -->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<!-- <scope>test</scope> -->
</dependency>
<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<!--commons-io-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!--guava-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
</dependencies>
</project>
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import org.apache.commons.io.FileUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import static org.apache.commons.io.IOUtils.copyLarge;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/10/13 21:15
*/
public class Test {
static Logger logger = Logger.getLogger(Test.class.getCanonicalName());
static ExecutorService executorService = Executors.newFixedThreadPool(20);
static final Map<String, String> mapTextStyle = ImmutableMap.of(
// "下載 updb 檔", "updb"
// "下載 prc 檔", "prc"
"下載直式 mobi 檔", "mobi"
//"下載 epub 檔", "epub"
);
/**
* @param args
* @return
* @description
* @author Liruilong
* @date 2020年10月15日 03:10:12
**/
public static void main(String[] args) {
String[] strings = new String[]{"http://www.haodoo.net/?M=hd&P=100", "http://www.haodoo.net/?M=hd&P=wisdom", "http://www.haodoo.net/?M=hd&P=history", "http://www.haodoo.net/?M=hd&P=martial", "http://www.haodoo.net/?M=hd&P=mystery",
"http://www.haodoo.net/?M=hd&P=scifi", "http://www.haodoo.net/?M=hd&P=romance", "http://www.haodoo.net/?M=hd&P=fiction"};
for (int j = 0; j < strings.length; j++) {
try {
Document doc = null;
doc = Jsoup.connect(strings[j]).get();
Elements s = doc.select("a[href]");
logger.info("爬取:" + strings[j] + "__________---------——————————————" + Thread.currentThread().getName());
List<Element> elements = s.stream().filter(a -> a.attr("abs:href").indexOf("book") != -1 && a.text().length() > 1)
.collect(Collectors.toList());
executorService.execute(() -> {
for (int i = 0; i < elements.size(); i++) {
try {
WebClient webclient = new WebClient();
logger.info("爬取:" + elements.get(i).text() + "__________---------——————————————" + Thread.currentThread().getName());
HtmlPage htmlpage = null;
htmlpage = webclient.getPage(elements.get(i).attr("abs:href"));
List<DomElement> domElements = htmlpage.getElementsByTagName("input").stream().filter(o ->
mapTextStyle.containsKey(o.getAttribute("value"))).collect(Collectors.toList());
for (int i1 = 0; i1 < domElements.size(); i1++) {
try {
String textNameStyle = mapTextStyle.get(domElements.get(i1).getAttribute("value"));
logger.info("爬取:" + elements.get(i).text() + "___" + textNameStyle + "_______---------——————————————" + Thread.currentThread().getName());
HtmlPage page = domElements.get(i1).click();
TimeUnit.SECONDS.sleep(1);
DomElement button = page.getElementById("okButton");
if (Objects.isNull(button)) {
TimeUnit.SECONDS.sleep(1);
}
final InputStream inputStream = button.click().getWebResponse().getContentAsStream();
saveFile(inputStream, elements.get(i).text(), textNameStyle);
} catch (Exception e) {
continue;
}
}
webclient.close();//关掉
} catch (IOException e) {
continue;
}
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @param io
* @param s
* @return 文件保存
* @description
* @author Liruilong
* @date 2020年10月15日 00:10:06
**/
public static void saveFile(InputStream io, String... s) {
executorService.execute(() -> {
logger.info("-----------------------------------------------------------------------导入开始:" + s[0] + "__________---------——————————————" + Thread.currentThread().getName());
try (OutputStream outputStream = new FileOutputStream(new File("G:\\codedemo\\src\\main\\" + s[0]
.replaceAll("【", "").replaceAll("】", "")
.replaceAll("《", "").replaceAll("》", "") + "." + s[1]));) {
copyLarge(io, outputStream);
outputStream.flush();
logger.info("-------------------------------------------------------------------------导入结束:" + s[0] + "__________---------——————————————" + Thread.currentThread().getName());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
爬取日志
写入数据
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)