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

投资有风险需谨慎。

相关推荐
新智元5 分钟前
MIT 惊人神作:AI 独立提出哈密顿物理!0 先验知识,一天破译人类百年理论
人工智能·openai
闰土_RUNTU12 分钟前
机器学习中的数学(PartⅡ)——线性代数:2.1线性方程组
人工智能·线性代数·机器学习
东锋1.316 分钟前
Spring AI 发布了它的 1.0.0 版本的第七个里程碑(M7)
java·人工智能·spring
邪恶的贝利亚29 分钟前
神经网络复习
人工智能·神经网络·机器学习
新智元30 分钟前
支付宝被 AI 调用,一句话运营小红书!国内最大 MCP 社区来了,开发者狂欢
人工智能·openai
岁月如歌,青春不败31 分钟前
AI智能体开发与大语言模型的本地化部署、优化技术
人工智能·深度学习·机器学习·大语言模型·智能体
学渣6765633 分钟前
【激活函数:神经网络的“调味料】
人工智能·深度学习·神经网络
陈明勇1 小时前
MCP 协议更新详解:从 HTTP+SSE 到 Streamable HTTP
人工智能·ai编程·mcp
视觉语言导航1 小时前
IJCV-2025 | 深圳大学记忆增强的具身导航!ESceme:基于情景记忆的视觉语言导航
人工智能·深度学习·具身智能
Better Rose1 小时前
【2025年泰迪杯数据挖掘挑战赛】B题 详细解题思路+数据预处理+代码分享
人工智能·数据挖掘