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

投资有风险需谨慎。

相关推荐
KaneLogger16 分钟前
AI模型与产品推荐清单20250709版
人工智能·程序员·开源
中电金信20 分钟前
中电金信 :十问高质量数据集:金融大模型价值重塑有“据”可循
人工智能·金融
吕永强20 分钟前
算法化资本——智能投顾技术重构金融生态的深度解析
人工智能·科普
Python×CATIA工业智造33 分钟前
详细页智能解析算法:洞悉海量页面数据的核心技术
爬虫·算法·pycharm
新智元37 分钟前
奥特曼:再也不和小扎说话!OpenAI 偷袭小扎马斯克,反手挖 4 核心员工
人工智能·openai
新智元41 分钟前
CS 专业爆冷,失业率达艺术史 2 倍!年入千万只需 5 年,大学却在禁 Cursor
人工智能·openai
代码能跑就行管它可读性1 小时前
【论文复现】利用生成式AI进行选股和分配权重
人工智能·chatgpt
阿里云大数据AI技术1 小时前
ODPS 15周年开发者活动|征文+动手实践双赛道开启,参与活动赢定制好礼!
大数据·人工智能·云计算
一颗小树x1 小时前
【机器人】复现 Aether 世界模型 | 几何感知统一 ICCV 2025
人工智能·机器人·世界模型·aether
Black_Rock_br1 小时前
语音交互新纪元:Hugging Face LeRobot如何让机器人真正“懂你”
人工智能·计算机视觉·机器人