背景
有时候,我们需要爬取一些网页数据给AI分析和推荐时,网页一般是有防爬虫的机制。我们可以通模拟人类方式去访问,通过一个无界面的浏览器,模拟点击,然后模拟等待,然后在爬取网页内容。
HtmlUnit
HtmlUnit 是一个用 Java 编写的、无头浏览器(headless browser) 。它不显示图形界面,但可以模拟浏览器的行为来访问网页内容、解析 HTML/JavaScript、操作表单、处理 cookies 等。
目标
爬取下面天天基金的排名-指数型(3248),保存为csv格式。

思路
主要判断页面是否加载完成,加载完后再执行动作(模拟点击)。我们可以尝试点击按钮(下一页,下一页),可以发现页面没有加载完,页面都会显示一个加载标识,可以通过这个标识存不存在来判断页面是否加载完成。
有人可能说为什么F12拿到地址,直接发请求。cookie呢?还有一些请求头?url上参数呢?
代码
mavney依赖
xml
<dependencies>
<dependency>
<groupId>org.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>4.11.1</version>
</dependency>
<dependency>
<groupId>org.htmlunit</groupId>
<artifactId>htmlunit-cssparser</artifactId>
<version>4.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.14</version>
</dependency>
</dependencies>
下面代码,加载基金排名页面,点击【指数型】,等待页面加载完成,爬取内容。点击【下一页】,等待页面加载完成,爬取内容,循环直到最后一页才返回。
Java
public static void main(String[] args) throws Exception {
try (final WebClient webClient = new WebClient()) {
final HtmlPage page = webClient.getPage("https://fund.eastmoney.com/data/fundranking.html");
MyDomChangeListener listener = new MyDomChangeListener();
HtmlElement li = page.querySelector("#types li[data-typeid='000']");
if (li != null) {
HtmlElement targetDiv = page.querySelector(".dbtable");
targetDiv.addDomChangeListener(listener);
//点击按钮,等待页面加载完成
listener.await(()->{
try {
HtmlPage newPage = li.click();
} catch (IOException e) {
e.printStackTrace();
}
});
HtmlElement selectLi = page.querySelector("#types .at");
System.out.println(selectLi);
boolean header = true;
while (true){
HtmlTable table = page.querySelector("#dbtable");
System.out.println(table);
List<List<String>> lists = parseTableToList(table);
if(!header){
lists = lists.subList(1,lists.size());
}else{
header = false;
List<String> subList = lists.get(0);
for (int i = 0; i < subList.size(); i++) {
String original = subList.get(i);
String replaced = original.replaceAll("\s+", "");
subList.set(i, replaced);
}
}
writeCsv(lists,"out.csv");
//查找当前页
HtmlElement curElement = page.querySelector("#pagebar .cur");
// 获取下一页
DomElement label = curElement.getNextElementSibling();
if (label != null) {
String classValue = label.getAttribute("class");
//判断最后一页返回
if(Arrays.asList(classValue.split(" ")).contains("end")){
break;
}
//点击按钮,等待页面加载完成
listener.await(()->{
try {
label.click();
} catch (IOException e) {
e.printStackTrace();
}
});
} else {
throw new RuntimeException("错误");
}
}
} else {
System.out.println("未找到目标 <li> 元素");
}
}
}
下面代码,通过监听元素的变化实现判断页面加载完成。
Java
static class MyDomChangeListener implements DomChangeListener {
private final ConcurrentLinkedQueue<ArrayBlockingQueue<Long>> queue = new ConcurrentLinkedQueue<>();
@Override
public void nodeAdded(DomChangeEvent domChangeEvent) {
if(domChangeEvent.getChangedNode() instanceof HtmlTable){
ArrayBlockingQueue<Long> peek = queue.peek();
if(peek!=null){
peek.clear();
long l = System.currentTimeMillis();
peek.offer(l);
}
}
}
@Override
public void nodeDeleted(DomChangeEvent domChangeEvent) {
}
public void await(Runnable action) throws InterruptedException {
ArrayBlockingQueue<Long> arrayQueue = new ArrayBlockingQueue<>(1);
queue.clear();
queue.offer(arrayQueue);
action.run();
arrayQueue.poll(5, TimeUnit.SECONDS);
}
}
完整代码:
运行结果
可以看到日期4-15的指数型基金排名是爬取出来了3263支。

AI使用
通过AI去帮我选择有价值的基金,但是发现文本太长,读不完里面内容。

我们可以通过Dify解决, <math xmlns="http://www.w3.org/1998/Math/MathML"> 3263 ∗ 0.17 = 554.71 3263*0.17=554.71 </math>3263∗0.17=554.71,ds大概一次性能分析500支,要分析7次。可以先将500支选出优质的50支,7次就有 <math xmlns="http://www.w3.org/1998/Math/MathML"> 7 ∗ 50 = 350 7*50=350 </math>7∗50=350,再350中选择10的优质基金,完成任务。

结果看样子和基金排名的前10一样,可能是提示词没有写好,应该打乱循序尝试一下。
投资有风险需谨慎。