找回密码
  注册[Register]
查看: 2579|回复: 11

[python] 股价翻番 人生赢家,python爬取基金 筛选股票

  [复制链接]
发表于 2020-1-7 16:32 | 显示全部楼层 |阅读模式
禁止求评分、诱导评分、互刷评分、互刷悬赏值,违规者封号处理。
禁止发布推广、邀请码、邀请链接、二维码或者有利益相关的任何推广行为。
所有非原创软件请发布在【精品软件区】,发帖必须按照本版块版规格式发帖。

本帖最后由 BiuBiu-大佬 于 2020-1-7 16:38 编辑

前言:
听说你想要变富?那就沉住气慢慢来吧,没听过这句名言么:“穷人总是不想慢慢的变富”。都想着一夜暴富,中个彩票啥的,可哪有那么幸运呢?总不能像我一样中了78万的彩票,然后自己偷偷的花吧。

想要慢慢的变富有,只要理好财就可以了;小钱靠攒,大钱靠赚!之前我也曾苦于思索如何让自己实现财富自由,所以才有了学习理财的想法,说到理财就不得不说一些理财产品,比如黄金、期货、股票、基金等,先普及一下这些小知识吧,因为就是爬取股票、基金的,所以就简单介绍一下这两者了;

股票(英语:stock)或是资本存货(英语:capital stock)是一种有价证券,股份公司将其所有权借由这种有价证卷进行分配。因为股份公司需要筹集资金,因此将股票发给投资者作为公司资本部分所有权的凭证,成为股东以此获得股息(股利),并分享公司成长或交易市场波动带来的利润;但也要共同承担公司运作错误所带来的风险。        ------来着 维基百科

基金,举个栗子吧,就是你手里有钱,想买股票,但是不懂股票的相关知识;我没钱,但有着非常丰富的金融知识、股票经验,是个理财的好手。于是我们一合计,你把你的钱给我,我来用这些钱理财,等赚钱了我从中拿点分成;“我”指的就是基金;        ------来自 星火燎愿 的自我理解

总体来说,股票是收益高,风险也高。基金是收益低,风险也低,因为基金买了好多的股票,买的股票也基本不会遇到全部涨或者全部跌的情况,所以抗风险性比较大。说到这里,假我不想买基金,就想买股票,还想买好的股票,但是我又不懂股票,咋办呢,额,好吧,我就是这么想的,虽然想得美,但是办法总是有的。我们分析下,基金算是买了很多股票的机构,并且里面都是各路财经大神,我们可以从基金里看到它们都买了哪些股票,然后跟着它们买不就ok了么,毕竟它们也不想让自己亏损,会挑选有潜力的股票的。

正文
本文就是利用python对某一财经网站的基金进行了爬取,爬取了5000+个基金所持有的股票,并进行了处理。

前阵因为爬取数据导致整个公司被抓的案例有不少,所以在此说明:拒绝利用爬虫进行违法的行为,坚决爱国爱民,做好事不留名,多扶老奶奶过马路,希望警察叔叔不要因为这篇爬虫文章把me带走。

本文涉及到的知识点:
1、python字符串:分割、拼接、中文字符判断;

2、python正则表达式;

3、爬虫requests请求库、xpath获取数据、代{过}{滤}理服务器;

4、selenium用法:无头浏览器、元素定位、显式等待、数据获取;

5、python操作mongodb

网站分析
代码和数据我们到后面再贴上,先来分析下目标网站,这样有利于我们爬取过程更加清晰;

目标网站:http://fund.eastmoney.com/data/fundranking.html#tall;c0;r;szzf;pn50;ddesc;qsd20181126;qed20191126;qdii;zq;gg;gzbd;gzfs;bbzt;sfbb

我们爬取的就是【开放式基金】里的数据:

我们随便点开一个基金,就可以进入其详情页面,不知道你发现没有,该基金详情页面的url就是首页该基金的基金代码和http://fund.eastmoney.com/的一个组合,比如:
        040011 --- 华安核心优选混合的url:http://fund.eastmoney.com/040011.html
        005660 --- 嘉实资源精选股票A的url:http://fund.eastmoney.com/005660.html
ok,好,我们在基金详情页面往下拉就可以找到该基金的股票持仓信息,,也就是该基金买了哪些股票:

然后点击 更多 进入该基金持股的详情页,往下拉就会看到,该基金三个季度的股票持仓信息:

对,这就是目标数据,要爬取的数据;
ok,我们先不爬取,再分析这个基金持仓的详情页,这个url也是有规律的,它是用 http://fundf10.eastmoney.com/ccmx_ 和该基金的基金代码组合成的,比如:
        005660 ,嘉实资源精选股票A 的持仓详情页面url:http://fundf10.eastmoney.com/ccmx_005660.html
        006921,南方智诚混合 的持仓详情页面url:http://fundf10.eastmoney.com/ccmx_006921.html

因为这些数据是用js动态加载的,如果使用requests爬取的话难度很大,这种情况下一般会使用selenium模拟浏览器行为进行爬取。但是selenium爬取的效率确实比较低。其实我们依旧是可以使用requests进行爬取的,js动态加载是html页面中的js代码执行了一段操作,从服务端自动加载了数据,所以数据在一开始爬取的页面上是看不到的,除非一些特别难爬的数据才需要selenium,因为selenium号称:只要是你看得到的数据就都可以获取。毕竟selenium是模仿人操作浏览器的行为的。这里我们分析js动态加载,然后利用requests来爬取,后面进行二次爬取的时候再用selenium。

在首页按F12打开开发者工具,然后再刷新一下,

可以看到右边蓝色框里的数据了吧,这是js动态加载之后返回的数据,然后经过加工后呈现在页面上的,其实只要获取这些数据就可以了,不用去爬取首页了;
我们再点击 Headers ,这个 Request URL 就是js请求的url了,你可以试试把这个url直接用浏览器回车下,会给你返回一堆的数据;上面分析了基金持仓股票页面url的组成,所以只要需要这些数据里的六位基金代码就可以了,本篇代码中是用python正则进行了六位数字的提取,然后组成的基金持仓股票页面的url;然后再在基金持仓股票页面对该基金持有的股票进行爬取、存储;
爬取流程:
1、首先从首页中请求js动态加载数据时请求的那个url,从中获取六位数字的基金代码,
然后 http://fundf10.eastmoney.com/ccmx_ + 基金代码 + .html  组成的基金持仓股票的详情页url;
2、针对 基金持仓股票的详情页url 进行爬取,因为也是js动态加载的(加载速度较快),并且需要判断该基金是否有持仓的股票(有的基金没有买股票,也不知道他们干啥了),所以使用selenium来爬取,同时也使用了显式等待的方式来等待数据加载完成;
3、将数据整理,存储到mongodb中;

代码讲解---数据爬取:
这次我们将代码分段放上来,分段说明;
需要的库:
  1. import requests
  2. import re
  3. from lxml import etree
  4. from selenium import webdriver
  5. from selenium.webdriver.chrome.options import Options
  6. from selenium.webdriver.common.by import By
  7. from selenium.webdriver.support.ui import WebDriverWait
  8. from selenium.webdriver.support import expected_conditions as EC
  9. import pymongo
复制代码
准备的一些常用方法:
  1. #判断字符串中是否含有中文
  2. def is_contain_chinese(check_str):
  3.     """
  4.     判断字符串中是否包含中文
  5.     :param check_str: {str} 需要检测的字符串
  6.     :return: {bool} 包含返回True, 不包含返回False
  7.     """
  8.     for ch in check_str:
  9.         if u'\u4e00' <= ch <= u'\u9fff':
  10.             return True
  11.     return False
  12. #selenium通过class name判断元素是否存在,用于判断基金持仓股票详情页中该基金是否有持仓股票;
  13. def is_element(driver,element_class):
  14.     try:
  15.         WebDriverWait(driver,2).until(EC.presence_of_element_located((By.CLASS_NAME,element_class)))
  16.     except:
  17.         return False
  18.     else:
  19.         return True
  20. #requests请求url的方法,处理后返回text文本
  21. def get_one_page(url):
  22.     headers = {
  23.         'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
  24.     }
  25.     proxies = {
  26.         "http": "http://XXX.XXX.XXX.XXX:XXXX"
  27.     }
  28.   
  29.     response = requests.get(url,headers=headers,proxies=proxies)
  30.     response.encoding = 'utf-8'
  31.     if response.status_code == 200:
  32.         return response.text
  33.     else:
  34.         print("请求状态码 != 200,url错误.")
  35.         return None
  36. #该方法直接将首页的数据请求、返回、处理,组成持仓信息url和股票名字并存储到数组中;
  37. def page_url():
  38.     stock_url = []      #定义一个数组,存储基金持仓股票详情页面的url
  39.     stock_name = []     #定义一个数组,存储基金的名称
  40.     url = "http://fund.eastmoney.com/data/rankhandler.aspx?op=ph&dt=kf&ft=all&rs=&gs=0&sc=zzf&st=desc&sd=2018-11-26&ed=2019-11-26&qdii=&tabSubtype=,,,,,&pi=1&pn=10000&dx=1&v=0.234190661250681"
  41.     result_text = get_one_page(url)
  42.     # print(result_text.replace('"',','))    #将"替换为,
  43.     # print(result_text.replace('"',',').split(','))    #以,为分割
  44.     # print(re.findall(r"\d{6}",result_text))     #输出股票的6位代码返回数组;
  45.     for i in result_text.replace('"',',').split(','):  #将"替换为,再以,进行分割,遍历筛选出含有中文的字符(股票的名字)
  46.         result_chinese = is_contain_chinese(i)
  47.         if result_chinese == True:
  48.             stock_name.append(i)
  49.     for numbers in re.findall(r"\d{6}",result_text):
  50.         stock_url.append("http://fundf10.eastmoney.com/ccmx_%s.html" % (numbers))    #将拼接后的url存入列表;
  51.     return stock_url,stock_name
  52. #selenium请求[基金持仓股票详情页面url]的方法,爬取基金的持仓股票名称;
  53. def hold_a_position(url):
  54.     driver.get(url)  # 请求基金持仓的信息
  55.     element_result = is_element(driver, "tol")  # 是否存在这个元素,用于判断是否有持仓信息;
  56.     if element_result == True:  # 如果有持仓信息则爬取;
  57.         wait = WebDriverWait(driver, 3)  # 设置一个等待时间
  58.         input = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'tol')))  # 等待这个class的出现;
  59.         ccmx_page = driver.page_source  # 获取页面的源码
  60.         ccmx_xpath = etree.HTML(ccmx_page)  # 转换成成 xpath 格式
  61.         ccmx_result = ccmx_xpath.xpath("//div[@class='txt_cont']//div[@id='cctable']//div[@class='box'][1]//td[3]//text()")
  62.         return ccmx_result
  63.     else:   #如果没有持仓信息,则返回null字符;
  64.         return "null"
复制代码
注意 page_url() 方法,里面的url就是上面分析js动态加载数据时请求的url,需要注意的是该url后面的参数,pi是第几页,pn是每页多少条数据,我这里pi=1,pn=10000,意思就是第一页,显示10000条数据(实际数据肯定没这么多,首页才5000+),就一次性的显示出所有的数据了;
程序开始:
  1. if __name__ == '__main__':
  2.     # 创建连接mongodb数据库
  3.     client = pymongo.MongoClient(host='XXX.XXX.XXX.XXX', port=XXXXX)  # 连接mongodb,host是ip,port是端口
  4.     db = client.db_spider  # 使用(创建)数据库
  5.     db.authenticate("用户名", "密码")  # mongodb的用户名、密码连接;
  6.     collection = db.tb_stock  # 使用(创建)一个集合(表)
  7.   
  8.     stock_url, stock_name = page_url()     #获取首页数据,返回基金url的数组和基金名称的数组;
  9.   
  10.     #浏览器动作
  11.     chrome_options = Options()
  12.     chrome_options.add_argument('--headless')
  13.     driver = webdriver.Chrome(options=chrome_options)    #初始化浏览器,无浏览器界面的;
  14.   
  15.     if len(stock_url) == len(stock_name):       #判断获取的基金url和基金名称数量是否一致
  16.         for i in range(len(stock_url)):
  17.             return_result = hold_a_position(stock_url[i])  # 遍历持仓信息,返回持仓股票的名称---数组
  18.             dic_data = {
  19.                 'fund_url':stock_url[i],
  20.                 'fund_name':stock_name[i],
  21.                 'stock_name':return_result
  22.             }        #dic_data 为组成的字典数据,为存储到mongodb中做准备;
  23.             print(dic_data)
  24.             collection.insert_one(dic_data)     #将dic_data插入mongodb数据库
  25.     else:
  26.         print("基金url和基金name数组数量不一致,退出。")
  27.         exit()
  28.   
  29.     driver.close()              #关闭浏览器
  30.   
  31.     #查询:过滤出非null的数据
  32.     find_stock = collection.find({'stock_name': {'$ne': 'null'}})  # 查询 stock_name 不等于 null 的数据(排除那些没有持仓股票的基金机构);
  33.     for i in find_stock:
  34.         print(i)
复制代码
好,至此,爬取数据的代码交代完毕,运行后坐等即可;该项目单进程运行,所以爬取速度略慢,同时也受网速影响,后期会继续改进成多线程。
代码讲解---数据处理:
上面已经把数据爬取并存储到数据库中,这里对数据进行处理,将其变成可用的;首先说明思路:1、我们需要知道这些基金所有持仓的股票的综合数据,也包括基金持仓中有重复的股票;2、需要知道哪些股票重复了,有多少个重复的,重复了多少次;这样,重复数最多的那只股票就肯定是最好的了,因为这证明有很多的基金都购买了这支股票;具体看代码,注释说的已经很清楚了:
  1. import pymongo
  2.   
  3. #一、数据库:连接库、使用集合、创建文档;#
  4. client = pymongo.MongoClient(host='XXX.XXX.XXX.XXX',port=XXXXX)  #连接mongodb数据库
  5.   
  6. db = client.db_spider       #使用(创建)数据库
  7. db.authenticate("用户名","密码")      #认证用户名、密码
  8.   
  9. collection = db.tb_stock    #使用(创建)一个集合(表),里面已经存储着上面程序爬取的数据了;
  10. tb_result = db.tb_data      #使用(创建)一个集合(表),用于存储最后处理完毕的数据;
  11.   
  12. #查询 stock_name 不等于 null 的数据,即:排除那些没有持仓股票的基金;
  13. find_stock = collection.find({'stock_name':{'$ne':'null'}})
  14.   
  15. #二、处理数据,将所有的股票数组累加成一个数组---list_stock_all #
  16. list_stock_all = []     #定义一个数组,存储所有的股票名称,包括重复的;
  17. for i in find_stock:
  18.     print(i['stock_name'])    #输出基金的持仓股票(类型为数组)
  19.     list_stock_all = list_stock_all + i['stock_name']   #综合所有的股票数组为一个数组;
  20. print("股票总数:" + str(len(list_stock_all)))
  21.   
  22. #三、处理数据,股票去重#
  23. list_stock_repetition = []  #定义一个数组,存放去重之后的股票
  24. for n in list_stock_all:
  25.     if n not in list_stock_repetition:        #如果不存在
  26.         list_stock_repetition.append(n)        #则添加进该数组,去重;
  27. print("去重后的股票数量:" + str(len(list_stock_repetition)))
  28.   
  29. #四、综合二、三中的得出的两个数组进行数据筛选#
  30. for u in list_stock_repetition:        #遍历去重后股票的数组
  31.     if list_stock_all.count(u) > 10:   #在未去重股票的数组中查找股票的重复数,如果重复数大于10
  32.         #将数据组成字典,用于存储到mongodb中;
  33.         data_stock = {
  34.             "name":u,
  35.             "numbers":list_stock_all.count(u)
  36.         }
  37.         insert_result = tb_result.insert_one(data_stock)    #存储至mongodb中
  38.         print("股票名称:" + u + " , 重复数:" + str(list_stock_all.count(u)))
复制代码
这样,就将数据稍微处理了一下存入了 tb_data 的集合中;下面只披露部分处理的数据:
  1. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62c9'), 'name': '水晶光电', 'numbers': 61}
  2. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62ca'), 'name': '老百姓', 'numbers': 77}
  3. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62cb'), 'name': '北方华创', 'numbers': 52}
  4. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62cc'), 'name': '金风科技', 'numbers': 84}
  5. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62cd'), 'name': '天顺风能', 'numbers': 39}
  6. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62ce'), 'name': '石大胜华', 'numbers': 13}
  7. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62cf'), 'name': '国投电力', 'numbers': 55}
  8. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62d0'), 'name': '中国石化', 'numbers': 99}
  9. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62d1'), 'name': '中国石油', 'numbers': 54}
  10. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62d2'), 'name': '中国平安', 'numbers': 1517}
  11. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62d3'), 'name': '贵州茅台', 'numbers': 1573}
  12. {'_id': ObjectId('5e0b5ecc7479db5ac2ec62d4'), 'name': '招商银行', 'numbers': 910}
复制代码


该数据还未做排序,排名不分先后;数据中:中国石化 的numbers是54,说明在5000+家的基金中有54家买了中国石化的股票;招商银行的numbers为910,说明在5000+家的基金中有910家基金买了招商银行的股票......额,好了,到此也没什么好说的了;最后,入市需谨慎,股票有风险;文章仅供学习,盈亏自负,概不负责;

免费评分

参与人数 1热心值 +1 牛币 +1 收起 理由
pangpang2009 + 1 + 1 我很赞同!

查看全部评分

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
发表于 2020-1-7 16:32 | 显示全部楼层
谢谢大牛
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 有用 没用

使用道具 举报

发表于 2020-1-7 16:34 | 显示全部楼层
好的,非常感谢
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 有用 没用

使用道具 举报

发表于 2020-1-7 16:34 | 显示全部楼层
感谢楼主分享
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 有用 没用

使用道具 举报

发表于 2020-1-7 16:36 | 显示全部楼层
6666666666
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 有用 没用

使用道具 举报

发表于 2020-1-7 16:36 | 显示全部楼层
谢谢分享
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 有用 没用

使用道具 举报

发表于 2020-1-7 16:44 | 显示全部楼层
6666
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 有用 没用

使用道具 举报

发表于 2020-1-7 16:48 | 显示全部楼层
谢谢大佬
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 有用 没用

使用道具 举报

发表于 2020-1-7 16:53 | 显示全部楼层
不买就是人生赢家,像你这中散户跟着吃虾米就已经差不多了。还在这里放点这种无聊的信息。。老股民会有空怕这里看你发的这些信息,不知道多少人都被套住了。。
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 有用 没用

使用道具 举报

发表于 2020-1-7 22:33 来自手机 | 显示全部楼层
感谢,感谢。
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心值】和【牛币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

RSS订阅|手机版|小黑屋|大牛论坛 |我的广告

GMT+8, 2024-5-10 08:20 , Processed in 0.044756 second(s), 21 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表