This project is a web scraper designed to fetch and parse data about funds from the EastMoney website. It uses Python and includes anti-scraping measures such as random User-Agent selection.
-
Clone the repository:
git clone https://github.com/yourusername/fund_crawler.git cd fund_crawler -
Install dependencies:
pip install -r requirements.txt
Run the main script to start the crawler:
python main.py
# CrawlFund总结报告
本项目可爬取天天基金网上的所有基金,辅助对基金投资的选择,包括基金代码,基金名称,单位净值,累计净值, 日增长率, 近1周, 近1月, 近3月, 近6月, 近1年, 近2年, 近3年, 今年来, 成立来的收益率等信息;实现了单个基金成立以来历史波动数据的爬取和基于echart的数据可视化,方便复盘投资决策;实现了多线程爬取二级网页的经理信息并写入csv
## 寻找思路
### 1.分析网站
1.首先来到[天天基金——基金排行]
通过基金简称中的超链接,我们可以跳转到某个基金的首页,观察到页面的网址由首页地址加上基金代码组成。在详情页我们可以得到基金规模、基金经理、历史单位净值等信息。
通过对比定位发现,实时的排行数据根据客户端渲染生成,fundranking.html中的<div class="space6">下的<table id="dbtable" >分割了每一行的数据,在排行首页我们要想办法获取到50个基金(当前页面)的**基金代码,历史收益率和单位净值等信息。**
### 2.抓包分析
在这里历史信息不在这个包中,应该在其他的包里,于是点击下一页,重新进行抓包。发现每次点击下一页时,都会发一个这个包
对这个包进行分析,发现响应是ASP格式(和之前的html不同),并且我们要的历史数据也在这里面。


2.在F12-Network的response中我们发现到带有上述信息的数据包封装在ASP函数中,我们将Request URL中的地址复制下来,开启一个新页面进行访问,发现响应的内容如下,aspx格式,但是很明显,内容并不是我们想要的,猜想应该是页面对这个请求地址做了反扒。
经过几次尝试,发现这个Request URL中的地址并不能直接访问,需要对请求投做特殊处理,也就是在发起对这个URL的请求时,请求头中要告诉浏览器,它是从那个网址跳转过来的,也就是不能直接访问这个URL,需要重借助另一个网址进行跳转,不然服务器会识别为爬虫行为,然后返回错误的数据。这个中间网址一般为Headers中的Referer,如下图所示。
所以在之后的代码编写环节要注意对headers的处理。
Request URL: [http://fund.eastmoney.com/data/rankhandler.aspx?op=ph&dt=kf&ft=all&rs=&gs=0&sc=6yzf&st=desc&sd=2021-04-11&ed=2022-04-11&qdii=&tabSubtype=,,,,,&pi=1&pn=50&dx=1&v=0.12011115027658459](Request URL: http:/fund.eastmoney.com/data/rankhandler.aspx?op=ph&dt=kf&ft=all&rs=&gs=0&sc=6yzf&st=desc&sd=2021-04-11&ed=2022-04-11&qdii=&tabSubtype=,,,,,&pi=1&pn=50&dx=1&v=0.12011115027658459)后面的字符串很容易看出来和排序方式、升降序、时间有关,把他去掉也不影响最后的响应内容。
最后剩下的就好判断了,一个是基金代码,一个是当前页面,还有一个是每页显示的数据量,有了这些在进行翻页就简单多了。
不过通过每次改变pageIndex进行翻页,我感觉有些麻烦,所以就尝试改变pageSize来在一个页面中获取所有的数据,没想到还真的可以,这样就可以指定pageSize的大小来获取数据,而不用翻页多次发请求,方便了许多。
\3.
## 代码编写
第一次伪装headers测试后发现 如果request URL请求带的参数错误天天基金会给你返回空白的 200结果。所以我们需要搞清楚Query String parameters的含义
通过不断的调包抓包发现,ft表示基金类型(股票、混合、QDII);sc表示排序方式,可根据不同时间周期的收益or净值排序;st表示升降序选择;sd、ed表示自定义时间周期(默认一年);pi:x 表示当前排序下从第x个基金开始,**pn表示response将发送的基金数量**。

第一阶段 我们的任务是抓取3000条基金的基本信息(近6月收益率降序排序)并将数据保存在csv文件中。
编写class GetPage 来处理请求和获取数据 接着class Processdata 来进一步储存数据

在解析完带参请求后把数据保存,可以将数据保存在数据库中持久化

第二阶段,我们想要实现爬取单个基金的历史净值数据
首先打开https://fundf10.eastmoney.com/jjjz_162719.html,进行翻页操作,查看后台请求Preview

分析完请求参数后根据同样的方法构造请求
这部分代码的主要难点是对每一天数据的处理分割和分页获取操作
对数据解析: 
在terminal的信息展示
使用yield generator 把每一页的数据添加到对象的属性中,实现数据的持久化,方便迭代器输出
最后生成的csv文件包含自成立以来的净值信息(若乱码 注意文件打开的编码格式根据操作系统设置,UTF-8 or GBK)
第三阶段,实现基金收益可视化
计划实现收益折线图和当日收益柱形图两种形式
\1. 导入pandas包处理csv数据,通过改变min_date可以实现统计已往任意时刻投入10000本金到现在的收益明细

导入pyecharts 实现可视化
pyecharts是一款将echarts封装到python中的一款开源框架,它提供了一个叫setOptions的方法,通过这个方法我们传入一个options就可以生成一个图表了,我们只需维护options变量。通过编辑Bar,Line,Grid对象,我们可以绘制柱形图,折线图和页面的布局
柱形图源代码
目录下生成的HTML: 
示例:生成的162719.html效果图

//也可导入snapshot_selenium包,输出png格式
第四阶段,为了爬取每个基金的详细信息,并发访问大量的URL,我们需要选择一种最适合该任务的编写方式。
有两个因素能够影响一个并发应用程序的性能:上下文切换和可扩展性。上下文切换在所有运行的任务间公平地共享 CPU 所需的工作,称为上下文切换,能够影响应用程序的性能。对同步应用程序来说,这项工作是由操作系统完成的,而且基本上是一个黑箱,不需要配置或微调选项。对异步应用程序来说,上下文切换是由循环完成的。默认的循环实现由asyncio提供,是用 Python 编写的,效率不是很高。
在以下场景时,我们可以说异步可能比同步快:存在高负载(没有高负载,访问的高并发性就没有优势)任务是 I/O 绑定的(如果任务是 CPU 绑定的,那么超过 CPU 数目的并发并没有帮助)你查看单位时间内的平均请求处理数。如果你查看单个请求的处理时间,甚至异步可能更慢,因为异步有更多并发的任务在争夺 CPU。
对于爬取3000+数据并将其写入文件的IO密集型任务,异步开发无疑是效率更高。
对于异步爬虫又有以下3种实现方式
1.多线程,多进程
好处:可以为相关的阻塞操作单独开辟线程或者进程,阻塞操作就可以异步执行
弊端:无法无限制的开启多线程或者多进程(如果不限制的开启了,会严重消耗CPU资源,这样会导致响应外界效率变慢)
2.线程池
好处:可以降低系统对进程或者线程创建和销毁的一个频率,从而降低系统的开销
弊端:线程池或者进程池使用有上限
3.单线程+异步协程
最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
Version.1 线程池+同步锁
启动另一个进程,并在这个进程中使用多线程(线程池)来爬取网页,线程的任务调度交给线程池处理。线程池的大小可在Config.py中设置,这里设置为4核*8 =32

爬取100条数据并同步写入csv的用时为41.646127 秒
我们可以看到爬取300条数据并同步写入csv的用时为133.6秒
当任务数>32时 随着数据量的增加,线程池开销成倍增加。
线程池内部是通过一个工作队列去维护任务的执行的。这将任务和线程分离,给了线程池优化线程数量的空间,解决了资源分配的问题,这些可以使应用程序和服务器应用响应更快。使用线程池是一个看似很不错的解决方案,但是它却有一个根本性的缺陷:连续争用问题。也就是多个线程在申请任务时,为了合理地分配任务要付出锁资源,对比快速的任务执行来说,这部分申请的损耗是巨大的。
Version.2 单线程+异步协程
asyncio是Python3.4版本引入的标准库,asyncio 往往是构建 IO 密集型的最佳选择。asyncio 提供一组 高层级 API 用于:
并发地 运行 Python 协程 并对其执行过程实现完全控制;
执行 网络 IO 和 IPC;
通过 队列 实现分布式任务; 
同步 并发代码;
爬取300条数据并同步写入csv的用时为55.25秒,使用主动出让的程序调度方式使得效率提升了接近100%

接下来可再设置一个交互界面 供用户选择获取基金详细信息的方式
## 总结
实现了3大功能:爬取基金的近1、3、6月,近1、3年及成立来的收益率,前基金经理及其任职时间、任职来的收益率及总的任职时间;任意基金的收益趋势图;在爬虫中使用异步实现高性能的数据爬取操作。
待发展功能:本地数据的索引排序,使用GEVENT框架的协程使用。