最近试图从财经网站上积累数据,选中了同花顺财经的数据中心的数据。

插一句话,个人认为同花顺可能是目前财经领域掌握着最先进的机器学习技巧与人工智能算法的网站了。这个网站,这种智能化的金融问答以及其叙述性的策略回测系统全网恐怕只此一家,确实是让人感到很惊艳。

言归正传,掌握了如此技术的同花顺对付几个爬虫可不是收到擒来。然而无论我用什么办法,我都只能获取到前五页的数据。下面我说明一下我的操作历程:

  1. 首先自然是常规的requests.Session()模块的方法,伪装User-Agent,先定位同花顺,再进入数据中心获取数据。结果甚至连访问都无法做到,后来我尝试复制了一段真实浏览器访问的Cookie加入到session.headers中终于可以实现数据获取;
  2. 然而接下来就是无穷无尽了只能获取到前五页的数据的问题了。我尝试了爬取每页伪装一个不同IP(失败,仍然只能五页);两次访问之间设置一个60~120秒的间隔(稍有起效,但是仍然会被拒绝访问);最后我甚至猜想会不会是同花顺默认设置如果访问了前五页就不允许访问接下来的页面,于是我尝试用一个随机的顺序去访问这总共71页,结果如你所料——并没有什么P用,访问了五页后照样拒绝访问;
  3. 我最后总结下来原因应该是出在Cookie上,可是我又无法做到每次访问换一个新的Cookie,虽然继续增加访问之间的间隔时间是有效果的,但是都间隔2分钟访问一次我还不如自己手动复制页面算了;
  4. 这时候我忽然想到了selenium。以前我觉得selenium能做到的事情,我普通爬虫照样能做到,而且selenium需要实际启动浏览器,刷新页面实在是很低效的做法,同样的爬虫我用requests库能爬100页而selenium可能只能爬到30页,因此我一度觉得selenium是一个很鸡肋的爬虫手段。但是现在不同了,requests.Session()模块在同花顺面前成了纸老虎,中看不中用了,于是我重启selenium来进行爬虫;
  5. 事实上在selenium只生成一个webdriver对象的情况下仍然会被同花顺限制在五页之类的访问次数,可是如果我每访问一次都重建一个新的webdriver对象则可以避免这种情况;

不多说,直接上代码↓↓↓

Tips:笔者是使用的Firefox浏览器,因此selenium是基于Firefox的,其他不同浏览器可以把代码中的Firefox改为Chrome之类的应该就可以了。另外请自行在该py文件所在的目录下新建一个用今日日期命名的文件夹(如2019-01-27)。

# -*- coding:UTF-8 -*- 

import os
import re
import json
import time
import numpy 
import pandas
import random
import requests

from bs4 import BeautifulSoup
from selenium import webdriver

"""
	作者:囚生CY
	平台:CSDN
	时间:2019/01/27
	转载请注明原作者
	创作不易,仅供分享
"""

class StraightFlush():
	def __init__(self,
		userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0",
		cookie="v=AigMPjtB4I7PKMwKRvAplY1u-Rc7UYxbbrVg3-JZdKOWPcYFimFc677FMG4x; log=; \
				Hm_lvt_78c58f01938e4d85eaf619eae71b4ed1=1548080158; \
				Hm_lpvt_78c58f01938e4d85eaf619eae71b4ed1=1548080196; \
				Hm_lvt_60bad21af9c824a4a0530d5dbf4357ca=1548080175; \
				Hm_lpvt_60bad21af9c824a4a0530d5dbf4357ca=1548080196; \
				Hm_lvt_f79b64788a4e377c608617fba4c736e2=1548080175; \
				Hm_lpvt_f79b64788a4e377c608617fba4c736e2=1548080196; \
				vvvv=1"
	):
		""" 定义构造函数传入可变参数 """
		self.userAgent = userAgent
		self.cookie = cookie
		
		""" 定义常用的固定参数 """
		self.date = time.strftime("%Y-%m-%d")							 # 获取类初始化的时间
		self.workSpace = os.getcwd()									 # 获取工作目录
		self.errorLog = "Error_Log.txt"
		self.mainURL = "http://www.10jqka.com.cn/"
		self.dataURL = "http://data.10jqka.com.cn/"
		
		self.orderSuffix = "field/code/order/asc/page/{}/ajax/1/"
		
		self.stockFlowURL = self.dataURL + "funds/ggzjl/"
		self.conceptFlowURL = self.dataURL + "funds/gnzjl/"
		self.industryFlowURL = self.dataURL + "funds/hyzjl/"
		
		self.stockTableURL = self.stockFlowURL + self.orderSuffix
		self.conceptTableURL = self.conceptFlowURL + self.orderSuffix
		self.industryTableURL = self.industryFlowURL + self.orderSuffix
		self.headers = {
			"User-Agent":self.userAgent,
			"Cookie":self.cookie,
		}
		self.session = requests.Session()
		self.session.headers = self.headers
		
		""" 初始化操作 """
		self.session.get(self.mainURL)
		
	def parse_money_flow_stock(self,):									 # 获取A股个股资金流动
		self.session.get(self.dataURL)
		html = self.session.get(self.stockFlowURL).text
		soup = BeautifulSoup(html,"lxml")
		spans = soup.find_all("span")				 	
		ths = soup.find_all("th")							
		""" 将<th>标签中的string作为DataFrame的header """
		flag = False
		with open(r"{}\{}\money_flow_stock_{}.csv".format(self.workSpace,self.date,self.date),"a") as f:
			for th in ths:		
				aLabel = th.find_all("a")
				string = aLabel[0].string if len(aLabel) else th.string
				if flag: f.write(",{}".format(string))
				else:
					flag = True
					f.write(str(string))
			f.write("\n")		
		""" 遍历<span>标签获取总页数 """
		for span in spans:
			string = str(span.string)
			if len(string)>2 and string[:2]=="1/":	
				page = int(string[2:])
				break
		""" 获取资金流动信息 """
		for i in range(1,page+1):
			b = webdriver.Firefox()	
			b.get(self.stockTableURL.format(i))
			html = b.page_source
			soup = BeautifulSoup(html,"lxml")
			trs = soup.find_all("tr")
			with open(r"{}\{}\money_flow_stock_{}.csv".format(self.workSpace,self.date,self.date),"a") as f:
				for tr in trs[1:]:										 # 跳过第一个<tr>标签是因为第一行是表头
					flag = False
					tds = tr.find_all("td")
					for td in tds:
						string = str(td.string)
						string = string.replace(" ","").replace("\t","").replace("\n","")
						if flag: f.write(",{}".format(string))
						else: 
							flag = True
							f.write(string)
					f.write("\n")
			b.quit()
		return True

	def parse_money_flow_concept(self,):								 # 获取A股概念板块资金流动
		self.session.get(self.dataURL)
		html = self.session.get(self.conceptFlowURL).text
		soup = BeautifulSoup(html,"lxml")
		spans = soup.find_all("span")				 	
		ths = soup.find_all("th")							
		""" 将<th>标签中的string作为DataFrame的header """
		flag = False
		with open(r"{}\{}\money_flow_concept_{}.csv".format(self.workSpace,self.date,self.date),"a") as f:
			for th in ths:		
				aLabel = th.find_all("a")
				if len(aLabel): 										 # 概念板块与行业板块读取表头的代码如此笨拙因为与个股的方法在这里竟然只能拿到None,而且两者格式一模一样,让我很奇怪
					tag = str(aLabel[0])
					index1 = tag.find(">")
					index2 = tag.find("<",index1)
					string = tag[index1+1:index2]
				else: string = th.string
				if flag: f.write(",{}".format(string))
				else:
					flag = True
					f.write(str(string))
			f.write("\n")		
		""" 遍历<span>标签获取总页数 """
		for span in spans:
			string = str(span.string)
			if len(string)>2 and string[:2]=="1/":	
				page = int(string[2:])
				break
		""" 获取资金流动信息 """
		for i in range(1,page+1):
			b = webdriver.Firefox()	
			b.get(self.conceptTableURL.format(i))
			html = b.page_source
			soup = BeautifulSoup(html,"lxml")
			trs = soup.find_all("tr")
			with open(r"{}\{}\money_flow_concept_{}.csv".format(self.workSpace,self.date,self.date),"a") as f:
				for tr in trs[1:]:										 # 跳过第一个<tr>标签是因为第一行是表头
					flag = False
					tds = tr.find_all("td")
					for td in tds:
						string = str(td.string)
						string = string.replace(" ","").replace("\t","").replace("\n","")
						if flag: f.write(",{}".format(string))
						else: 
							flag = True
							f.write(string)
					f.write("\n")
			b.quit()
		return True

	def parse_money_flow_industry(self,):								 # 获取A股概念板块资金流动
		self.session.get(self.dataURL)
		html = self.session.get(self.industryFlowURL).text
		soup = BeautifulSoup(html,"lxml")
		spans = soup.find_all("span")				 	
		ths = soup.find_all("th")							
		""" 将<th>标签中的string作为DataFrame的header """
		flag = False
		with open(r"{}\{}\money_flow_industry_{}.csv".format(self.workSpace,self.date,self.date),"a") as f:
			for th in ths:		
				aLabel = th.find_all("a")
				if len(aLabel): 										 # 概念板块与行业板块读取表头的代码如此笨拙因为与个股的方法在这里竟然只能拿到None,而且两者格式一模一样,让我很奇怪
					tag = str(aLabel[0])
					index1 = tag.find(">")
					index2 = tag.find("<",index1)
					string = tag[index1+1:index2]
				else: string = th.string
				if flag: f.write(",{}".format(string))
				else:
					flag = True
					f.write(str(string))
			f.write("\n")		
		""" 遍历<span>标签获取总页数 """
		for span in spans:
			string = str(span.string)
			if len(string)>2 and string[:2]=="1/":	
				page = int(string[2:])
				break
		""" 获取资金流动信息 """
		for i in range(1,page+1):
			b = webdriver.Firefox()	
			b.get(self.industryTableURL.format(i))
			html = b.page_source
			soup = BeautifulSoup(html,"lxml")
			trs = soup.find_all("tr")
			with open(r"{}\{}\money_flow_industry_{}.csv".format(self.workSpace,self.date,self.date),"a") as f:
				for tr in trs[1:]:										 # 跳过第一个<tr>标签是因为第一行是表头
					flag = False
					tds = tr.find_all("td")
					for td in tds:
						string = str(td.string)
						string = string.replace(" ","").replace("\t","").replace("\n","")
						if flag: f.write(",{}".format(string))
						else: 
							flag = True
							f.write(string)
					f.write("\n")
			b.quit()
		return True

if __name__ == "__main__":
	print("测试开始...")
	sf = StraightFlush()
	sf.parse_money_flow_stock()
	sf.parse_money_flow_concept()
	sf.parse_money_flow_industry()