神器 Selenium 专治上交所妖蛾子 - Jacky Liu's Blog

神器 Selenium 专治上交所妖蛾子

Jacky Liu posted @ 2013年3月08日 16:10 in Python with tags python Selenium 上交所 股票列表 抓取 , 4066 阅读

    ---- 上交所又出妖蛾子了,前一阵子股票列表查询页面的 url 又不能用,第三次了。

    ---- 一直以来我都从上交所网站上抓取股票列表信息。网站对应的页面用的是动态技术,网址是由 javascript 生成,对访问者隐藏的。没关系,用抓包分析,就能看见如下一个网址:

        http://www.sse.com.cn/sseportal/webapp/datapresent/SSEQueryStockInfoAct?reportName=BizCompStockInfoRpt&PRODUCTID=&PRODUCTJP=&PRODUCTNAME=&keyword=&tab_flg=1&CURSOR={cursor}

    {cursor}是替换部分,程序只要替换成每页的起始偏移就能抓取到全部的股票列表。但是过了一年多以后,这个 url 不能用了。

    ---- 仍然抓包,发现这次访问的 url 是如下形式:

        http://query.sse.com.cn/commonQuery.do?jsonCallBack=jsonpCallback51734&isPagination=false&sqlId=COMMON_SSE_ZQPZ_GPLB_MCJS_SSAG_L

    这次还真是改进。与前面那个需要翻页不同,这个只要一次访问就返回全部股票列表,两边都省事,而且返回字串是符合 Python 语法的,一个 eval() 函数就能转化成 Python 数据格式。可是好景不长,几个星期之后这个 url 又不能用了。

    ---- 再来,这回发现 url 改动不大,只是 "jsonpCallback" 后面那一个数字也变成了动态的,每一次访问都不一样。这样一来,除非程序里也包含 javascript 引擎,能根据页面里的 js 代码算出那个值是多少,否则别想获取完整的股票列表了。就算这种方式也只是理论上可以,现实有没有途径还不知道。

    ---- 这种改动很微妙,目测其目的就是为了防止我这样的人用程序抓取它的页面信息。大概只要你访问了他的网站,他就自动假设你是个小散,而小散当然只配用手一页页翻着看。给机构和服务商都提供有专用的高速数据接口,一年几十万数据费不过湿湿碎而已。相比之下,深交所是把所有股票的代码、简称、公司信息都做成一张表,方便下载。上交所真够有心计。

    ---- 上交所到底属于什么性质真不好说。不过我总觉得,它多少应该带这么一点公共服务的性质。既然不准各地都建交易所,互相竞争,谁的服务好就到谁那里去上市,既然全国只有两个交易所,上市公司只有两千多家,一个 IPO 券商要收几千万。那么交易所在高价贩卖行情信息之余,稍微附带地给大众提供那么一点免费的服务,比如说查询一下上市股票有多少个,叫什么名字,代码多少之类,应该是理所当然的吧?

    ---- 既然要提供的话,就像深交所那样,做个列表给大家下载,你的服务器也能减轻负担,因为只要一次访问就成,小散们用着也方便,这也毫不过分吧?

    ---- 但是现实中奈何不了小人。不仅不提供下载,还想办法禁止老子抓取。禁NMGB。什么 javascript 老子是一概不懂的,就算懂也没功夫跟小人玩猫捉耗子。一通 Google 之后,搜到了一个神器: Selenium。神器是干什么的呢?官网里说的明白:

"Selenium automates browsers. That's it."

在老子看来,就是专门对付小人用的。

    ---- 代码,不到 60 行就搞定。注意 Selenium 还不支持 py3,这些是 py2 的:
 

# -*- encoding: utf-8 -*-

import sys
import pickle

import selenium

from selenium.webdriver.support.ui import WebDriverWait			# available since 2.4.0
#	from selenium.common.exceptions import TimeoutException
#	from selenium.webdriver.support import expected_conditions as EC	# available since 2.26.0

def wait_condition_01(driver):
	return driver.find_element_by_id('dateList_container_pageid')

def extract_table(driver, stocklist):
	tag_table= driver.find_element_by_class_name("tablestyle")
	tabletext= tag_table.text
	stocklist.extend(tabletext.split('\n')[1:])

driver= selenium.webdriver.Firefox()
driver.get("http://www.sse.com.cn/assortment/stock/list/name/")

stocklist= []
extract_table(driver=driver, stocklist=stocklist)

tag_meta= driver.find_element_by_id("staticPagination")
attr_total= int(tag_meta.get_attribute("total"))
attr_pageCount= int(tag_meta.get_attribute("pageCount"))

# 逐页提取内容
for pagenr in range(2, attr_pageCount+1):
	id_input= 'dateList_container_pageid' if pagenr > 2 else 'xsgf_pageid'
	id_button= 'dateList_container_togo' if pagenr > 2 else 'xsgf_togo'
	
	tag_input= driver.find_element_by_id(id_input)
	tag_button= driver.find_element_by_id(id_button)
	tag_input.send_keys(str(pagenr))
	tag_button.click()
	WebDriverWait(driver, 10).until(wait_condition_01)

	extract_table(driver=driver, stocklist=stocklist)

# 向主调进程发送结果
data= {
	'个股总数': attr_total,
	'个股列表': stocklist,
}

driver.quit()
pdata= pickle.dumps(data, protocol=2)
sys.stdout.write( pdata + b'\n' )

 

    ---- 这下,除非你上交所的网站彻底不开了。否则老子就是要自动抓取你的股票列表。看着办吧。
 

Avatar_small
crosser 说:
2013年9月20日 17:48

你是说下面这个地址是一直在变化的?http://www.sse.com.cn/js/common/ssesuggestdataAll.js

Avatar_small
Jacky Liu 说:
2013年9月21日 23:39

@crosser: 不是,是文里贴的那个网址,中间一部分每次访问都不一样。你的这个网址是怎么得到的。是分析上交所页面的 js 代码看出来的吧?

Avatar_small
crosser 说:
2013年9月22日 21:35

@Jacky Liu: 是的,不过还是你那种方法比较妥当一些,感谢分享,我才开始学Matplotlib for Python

Avatar_small
Jacky Liu 说:
2013年9月22日 21:46

@crosser: 我那么搞是因为不懂 js。也曾看过网站里的 js 代码,不得要领。只会用用封包分析,封包再不顶用只能另起炉灶了。

matplotlib 很强也好用,祝顺利。

Avatar_small
cygnus 说:
2015年6月02日 00:13

牛人专治小人。:)

Avatar_small
Eric 说:
2015年9月01日 11:33

callback 的算法倒是简单,但是可能不止这一种,header里面也有限制。

jsonpCallback:"jsonpCallback"+Math.floor(Math.random() * (100000000 + 1)),

Avatar_small
Jacky Liu 说:
2015年9月01日 12:12

@Eric: 不懂 js,不过既然有 random 函数在里面,大概九成是不可做的。

应该就是为了防止机器抓取,真是恶毒。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter
Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee