毕业设计--基于neo4j的股票筛选系统

本文记录了使用Java、Spring Boot和Neo4j开发股票筛选系统的进程,包括对东方财富网数据的爬取,利用Neo4j存储股票信息,并以知识图谱形式展示。遇到的挑战包括动态数据获取、页面跳转、数据去重等,通过htmlunit和jsoup结合处理HTML内容。目前工作已实现股票信息的抓取和去重,下一步将进行数据持久化和选股功能开发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目介绍

通过对东方财富网的盈利预测板块爬虫,拿到股票的每股盈利预测,结合该股的现市值,就可算出股票的预测市盈率,从而帮助我们筛选股票。而neo4j是一个图形数据库,我们将股票信息存入neo4j中并且以知识图谱的形式展现出来。

开发日志

4.9及之前

首先我们要完成对东方财富的爬虫工作,一开始打算爬的是个股研报板块,因为市盈率他帮你计算好了,选用jsoup,jsoup的大致流程是创建一个httpclient-->创建get或者post请求-->获取响应-->获取页面内容 

public void testJsoup() throws Exception {
        // 创建HttpClient
        CloseableHttpClient httpClient = HttpClients.createDefault();

        // 创建GET请求
        HttpGet httpGet = new HttpGet("https://reportapi.eastmoney.com/report/list?cb=datatable8079493&industryCode=*&pageSize=50&industry=*&rating=&ratingChange=&beginTime=2020-12-24&endTime=2022-12-24&pageNo=5&fields=&qType=0&orgCode=&code=*&rcode=&p=5&pageNum=5&pageNumber=5&_=1671854592251");
        httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");

        // 获取响应
        CloseableHttpResponse response = httpClient.execute(httpGet);

        // 获取页面内容
        if (response.getStatusLine().getStatusCode() == 200) {
            String html = EntityUtils.toString(response.getEntity(), "UTF-8");

            // 创建Document对象
            Document document = Jsoup.parse(html);
            Element element=document.body();
            StringBuffer stringBuffer=new StringBuffer();
//            System.out.println(element.text());
//            String strReg="datatable.?[(]";
            String json=element.text().substring(17);//前17位无用
            JSONObject jObject1=new JSONObject(json);
            //获取对象中的数组
            JSONArray data = jObject1.getJSONArray("data");
            for(int i=0;i<data.length();i++){
                JSONObject jObject2=data.getJSONObject(i);
                String stockName=jObject2.getString("stockName");
                String stockCode=jObject2.getString("stockCode");
                String predictNextTwoYearEps=jObject2.getString("predictNextTwoYearEps");
                String predictNextTwoYearPe=jObject2.getString("predictNextTwoYearPe");
                String predictNextYearEps=jObject2.getString("predictNextYearEps");
                String predictNextYearPe=jObject2.getString("predictNextYearPe");
                String predictThisYearEps=jObject2.getString("predictThisYearEps");
                String predictThisYearPe=jObject2.getString("predictThisYearPe");
              stringBuffer
//                      .append(stockName).append(",")
                        .append(stockCode).append(",")
                        .append(predictNextTwoYearEps).append(",")
                        .append(predictNextTwoYearPe).append(",")
                        .append(predictNextYearEps).append(",")
                        .append(predictNextYearPe).append(",")
                        .append(predictThisYearEps).append(",")
                        .append(predictThisYearPe).append(",")
                        .append("\n");
            }
            File newfile=new File("D:\\个股研报\\test.csv");//待写入文件
            BufferedWriter bufferedWriter=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newfile),"UTF-8"));

            bufferedWriter.write(stringBuffer.toString());
            bufferedWriter.close();
            System.out.println("文件写入内容完成");


            response.close();
            httpClient.close();
        }

    }

很快遇到了第一个问题:为什么打印出的html内容没数据?经过F12检查network发现该网页的数据是通过js获取的,请求的url是这样的

https://reportapi.eastmoney.com/report/list?cb=datatable8079493&industryCode=*&pageSize=50&industry=*&rating=&ratingChange=&beginTime=2020-12-24&endTime=2022-12-24&pageNo=5&fields=&qType=0&orgCode=&code=*&rcode=&p=5&pageNum=5&pageNumber=5&_=1671854592251

response就是一个名为datatable8079493的json,这下又犯难了,一个是url末尾的_=1671854592251和datatable8079493不知道规律,那就不能写出下一面的url;还有一个问题是response的datatable8079493长度不定,也就不好把他从数据中剥离出来。好消息是response有比html上显示的更多的预测内容,它有预测到后两年的市盈率,而html中只有今年和明年的。

此时我又了解到另一个工具包htmlunit,它可以完成浏览器行为的模拟,例如点击。于是我开始使用htmlunit进行爬虫,它的工作流程是创建一个webclient-->拿htmlpage-->处理内容

public void UnitTest() throws Exception{
      
        WebClient webClient=new WebClient();
        //禁止css加载
        webClient.getOptions().setCssEnabled(false);//禁止css
        webClient.getOptions().setJavaScriptEnabled(true);//允许js
        HtmlPage page=webClient.getPage("https://data.eastmoney.com/report/profitforecast.jshtml");
        webClient.waitForBackgroundJavaScript(2000);
         System.out.print(firstPage.asXml());

    }

因为htmlunit对css和js的兼容比较差,所以一般来说都要关闭,但是我们的数据是通过js获取的,当然不能关,还有要等待js完成,所以有

webClient.waitForBackgroundJavaScript(2000);

也就是等待两千毫秒。

页面成功拿到了,问题又出现了。

这个table表没id啊,htmlunit官方文档里对table只有通过id获取,相当难办。

4.10

今天灵机一动,既然jsoup的document对象是用html的String类作为参数创建,而jsoup可以通过类名来查询,那我们不是可以用htmlunit来完成跳转下一面,再返回html页面给jsoup处理吗?所以今天就完成htmlunit跳转测试。

跳转就是找到对应的a标签并点击。

List<HtmlAnchor> ao = index.getByXPath("//a");
HtmlAnchor which = null;
for(HtmlAnchor h :ao) {
    String href = h.getAttribute("data-page").toString();//拿到datapage属性
    if(href.equals("2")) {//如果datapage等于2
//      System.out.println(href);
        which = h;
        break;
    }
}

//点击跳转到第二面
HtmlPage index2 = which.click();

4.11

今天完成跳转到下一面并把每一面的html内容放入集合容器中。

1.选集合容器

我们的需求是有序且不重复,所以选linkedhashset。为什么linkedhashset有序?因为它是继承hashset的,而hashset是用hashmap实现的,底层是数组加链表和红黑树。

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable {
    static final long serialVersionUID = -5024744406713321676L;
    private transient HashMap<E, Object> map;
    private static final Object PRESENT = new Object();

hashset在添加元素时用的是hashmap的put方法,它把元素作为key,把PRESENT作为value,当key相同时,value也相同,此时就会用新的value替换旧value,也就达到了不重复的要求。

2.如何判断哪个超链接是下一页?

声明一个pageNum的整型变量代表当前页面值,在if的判断条件中将data-page转成整型来比对当前页面值加1。

Integer.parseInt(href) == pageNum+1

运行报错:href为"",也就是说在某次循环中,href为空串,说明有的datapage的值是空值,虽然按理来说datapage的值只会是数字或者空值,但是为了程序的复用性更好,我们用正则表达式判断href的值是否为数字。

Pattern p=Pattern.compile("^[0-9]{1,2}$");
                if (p.matcher(href).matches() && Integer.parseInt(href) == pageNum+1) {
  

3.我们找到了下一面但是如何让这一过程重复下去呢?

双层循环

我们先将我们当前页面的html传入容器中,再跳到下一面,将HtmlPage的值指向我们当前这一面且pageNum加一。但是什么时候停止呢?就是到最后一面嘛。可是什么时候到最后一面?也就是说我们要找到最大页码值,当当前页码值等于最大页码值就可以了。

如何取得最大页码值?

1.取得list的倒数第二个

可以看到a标签倒数第二个就是最大页码,但是谁能保证它是不是倒数第二呢?html中有很多很多超链接,随便加些a标签就会使这个方法失效。不可取


2.写一个getmax的方法,将list中的字符串转换成整数并判断大小

我们可以拿出所有的data-page比大小嘛,找出最大的data-page就好啦,但是有点麻烦。


3.判断data-page是否是pageNum+1,是的就跳转并break;加一个理想page值,值为当前page值+1,当出循环时,若page不是理想page值时,就break结束循环。

因为pageNum是当前页码,我们想要datapage为pageNum加一的超链接,我们希望每次出循环时我们都完成了跳转,pageNum是之前的加一,但是到最后一面的时候,并没有这个datapage,因为此时pageNum最大,我们没有跳转,也就是说与我们的期望值不符。

这个期望值是借鉴快速失败机制

快速失败也就是在迭代容器时,不允许改变其内容,否则就会抛出ConcurrentModificationException异常。

其原理就是迭代器在迭代的时候有一个modCount值以及一个expectedModCount值,当访问下一个元素前,迭代器会检

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lock1at

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值