Java爬虫:HtmlUnit爬取基金排名,AI分析数据(DeepSeek,Dify)

背景

有时候,我们需要爬取一些网页数据给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一样,可能是提示词没有写好,应该打乱循序尝试一下。

投资有风险需谨慎。

相关推荐
大龄程序员狗哥6 小时前
第47篇:使用Speech-to-Text API快速构建语音应用(操作教程)
人工智能
KKKlucifer6 小时前
数据安全合规自动化:策略落地、审计追溯与风险闭环技术解析
人工智能·安全
RWKV元始智能7 小时前
RWKV超并发项目教程,RWKV-LM训练提速40%
人工智能·rnn·深度学习·自然语言处理·开源
dyj0957 小时前
Dify - (一)、本地部署Dify+聊天助手/Agent
人工智能·docker·容器
墨染天姬7 小时前
【AI】Hermes的GEPA算法
人工智能·算法
小超同学你好7 小时前
OpenClaw 深度解析系列 · 第8篇:Learning & Adaptation(学习与自适应)
人工智能·语言模型·chatgpt
紫微AI7 小时前
前端文本测量成了卡死一切创新的最后瓶颈,pretext实现突破了
前端·人工智能·typescript
码途漫谈7 小时前
Easy-Vibe开发篇阅读笔记(四)——前端开发之结合 Agent Skills 美化界面
人工智能·笔记·ai·开源·ai编程
易连EDI—EasyLink7 小时前
易连EDI–EasyLink实现OCR智能数据采集
网络·人工智能·安全·汽车·ocr·edi
冬奇Lab7 小时前
RAG 系列(二):用 LangChain 搭建你的第一个 RAG Pipeline
人工智能·langchain·llm