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一样,可能是提示词没有写好,应该打乱循序尝试一下。

投资有风险需谨慎。

相关推荐
2501_920953866 小时前
工业4.0时代,制造企业精益管理咨询的标准化实施步骤
大数据·人工智能·制造
~央千澈~6 小时前
《2026鸿蒙NEXT纯血开发与AI辅助》第四章 对鸿蒙next项目结构目录详解以及实战解决一个最初的依赖安装的报错·卓伊凡
人工智能
2501_916007476 小时前
网站爬虫原理,基于浏览器点击行为还原可接口请求
前端·javascript·爬虫·ios·小程序·uni-app·iphone
xinlianyq6 小时前
2026企业流量破局:四大主流短视频矩阵获客系统深度解析与选型指南
人工智能·矩阵
workflower7 小时前
用硬件换时间”与“用算法降成本”之间的博弈
人工智能·算法·安全·集成测试·无人机·ai编程
Cx330❀8 小时前
一文吃透Linux System V共享内存:原理+实操+避坑指南
大数据·linux·运维·服务器·人工智能
OPHKVPS8 小时前
Anthropic 为 Claude Code 推出“自动模式”:AI 编码工具迈向更高自主性
网络·人工智能·安全·ai
Allen_LVyingbo8 小时前
斯坦福HAI官网完整版《2025 AI Index Report》全面解读
人工智能·数学建模·开源·云计算·知识图谱
金融小师妹8 小时前
基于AI通胀预期建模与能源冲击传导机制的政策分析:高频信号下的风险再评估
人工智能·svn·能源
胡摩西8 小时前
当大模型遇上毫米级定位:机器人将拥有“空间思维”?
人工智能·机器人·slam·gps·室内定位·roomaps