我们这次使用S&P Capital IQ NetAdvantage来查找S&P500中市值超过$50B的公司,并将表单数据爬取下来存入数据库。最后根据自己的需求找到对应的股票。
为了更好地了解整个过程,建议使用python的交互式界面。
使用工具
- Selenium
- Chrome webdriver
- mysqlConnector
- re
使用Selenium驱动浏览器
首先我们在这里先下载Webdriver, Selenium支持很多主流浏览器,如....。我们以Chrome为例,
先初始化一个浏览器实例,
1 | >>>driver = webdriver.Chrome() |
这时会启动一个新的Chrome浏览器界面,和我们平时的浏览器不同的是,这个新的浏览器是和我们python中的driver之间是可以通信的。当我们对浏览器(或driver实例)进行操作时,对应的driver(或浏览器)会相应改变。
比如我们将网站跳转到www.Google.com:
1 | >>>driver.get("http://www.google.com") |
我们可以看到浏览器的第一个标签页跳转到了Google.com
接着我们在浏览器中手动输入www.baidu.com并进入
1 | >>>driver.current_url |
就可以得到
1 | 'http://www.baidu.com/'
|
接着我们进入到NetAdvantage的主页,点击Screener并添加筛选条件,SP500中市值大于$50B的公司。大约有一百个。
好了接着我们就可以使用Python的re库来爬取我们想要的资料了
re解析网页源码
在抓取我们想要的数据之前我们需要先获得网页的源代码,我们使用driver的page_source方法获得浏览器当前页面源代码。注意和我们之前使用urllib和request库不同的是。由于我们使用浏览器进行加载,我们可以直接获得AJAX及其他各种方法加载过后的内容。为了提高效率,有些网站会有意无意地使用AJAX技术,这使我们之前的爬虫只能获得加载前的网页源码,并不能找到我们需要的数据,从而增加了我们的爬取难度。
先来获取加载后的网页源码
1 | >>>source = driver.page_source |
接着我们来引入我们的re库
1 | >>>import re |
来看看我们要抓的数据是什么样子的结构
<tr class="trowB_na"> <td width="20" height="17"><input type="checkbox" name="displayRow" value="4" onclick="Toggle(this,'trowB_na')"></td> <td height="17" align="left" ><a href=/NASApp/NetAdvantage/showPublication.do?dataPosition=4&SPID=5247 >ConocoPhillips</a></td> <td height="17" align="left" ><a >COP</a></td> <td height="17" align="left" ><a >US</a></td> <td height="17" align="left" ><a >Energy</a></td> <td height="17" align="left" ><a >Oil & Gas Exploration & Production</a></td> <td height="17" align="left" ><a >48.760</a></td> <td height="17" align="left" ><a >3 Star</a></td> <td height="17" align="left" ><a >N/A</a></td> <td height="17" align="left" ><a >40.04</a></td> <td height="17" align="left" ><a >2.45</a></td> <td height="17" align="left" ><a >N/A</a></td> <td height="17" align="left" ><a >35.38</a></td> </tr>
猛的一看我们的表单这么复杂,其实很简单。我们来看看都有什么,其中tr标签代表一行,td标签代表一个单元格:
- Company Name,Ticker,
- Region,
- S&P Sub-Industry,
- Last Closing Price,
- S&P STARS Ranking,
- S&P Capital IQ Quantitative Recommendation,
- 1yr Total Return %,
- 5yr Total Return %,
- P/E,
- LTD as % of Total Capital
除第一行
<td width="20" height="17"><input type="checkbox" name="displayRow" value="4" onclick="Toggle(this,'trowB_na')"></td>
之外,其余的td标签下的内容都是我们需要的数据。所以我们可以把正则表达式写成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <tr class="trow\w_na">.*? <td.*?</td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? <td.*?<a.*?>(.+?)</a></td>.*? </tr> |
这样我们就可以抓取(.+?)所代表的数据了,而.*?为不明确的内容被忽略掉。我们来用re.compile方法构成一个正则表达式的pattern
>>>regex = '<tr class="trow\w_na">.*?<td.*?</td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?<td.*?<a.*?>(.+?)</a></td>.*?</tr>' >>>pattern = re.compile(regex,re.S)
接着使用re.findall来使用pattern查找页面源代码source里面所有匹配的内容
>>>company_data_list = re.findall(pattern,source)
得到的内容如下
>>>company_data_list [('Oracle Corp', 'ORCL', 'US', 'Information Technology', 'Systems Software', '40.230', '5 Star', 'N/A', '16.99', '8.59', '19.20'), ('Amazon.com Inc', 'AMZN', 'US', 'Consumer Discretionary', 'Internet & Direct Marketing Retail', '830.380', '4 Star', 'N/A', '43.27', '33.73', '190.00'), ...]
好了我们的爬取工作已经完成,接下来我们写入MySQL数据库
写入数据库
首先我们可以登入我们的MySQL
$ mysql -u [username] -p [password]
输入密码后
Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 17 Server version: 5.7.9 MySQL Community Server (GPL) Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
接着我们要创建一个数据库,名字叫test,字符编码UTF-8
mysql> CREATE DATABASE test charset = 'utf8';
接着进入我们的数据库
mysql> USE test;
创建一张表,名字叫sp100,同时包含了表格各列的名称和数据类型,由于我们抓取的内容都是字符串,并且有的内容标示为'N/A',我们不能使用数字格式,这里使用字符串格式;
create table sp10( Company_Name varchar(50), Ticker varchar(10), Region varchar(10), SP_Sector varchar(30), SP_Sub_Industry varchar(50), Last_Closing_Price varchar(10), Ranking Varchar(15), Capital_IQ_RECOM VARCHAR(10), Return_1yr varchar(10), Return_5yr varchar(10), PE varchar(30), LTD varchar(30) );
接着我们就可以使用python继续写入内容了
首先引入mysql Connector
1 | >>>import mysql.connector |
接着创建一个你的数据库信息变量config
1 2 3 4 5 6 7 | config={'host':'127.0.0.1', 'user':'你的用户名', 'password':密码, 'port':3306, 'database':'test', #这是你刚刚创建的数据库test 'charset':'utf8', #编码'utf8' } |
然后尝试连接数据库,出错则报错
1 2 3 4 | try: cnn = mysql.connector.connect(**config) except mysql.connector.Error as e: print('connnect failed!{}'.format(e)) |
通过连接创建一个控制数据库的光标cursor,它会代替你来对数据库执行操作
1 | >>>cursor = cnn.cursor() |
然后给一个你将要执行的INSERT语句来把我们的变量插入,%s表示要插入的数据
1 | >>>insert_statement = 'Insert sp100 (Company_Name,Ticker,Region,SP_Sector,SP_Sub_Industry,Last_Closing_Price,Ranking,Capital_IQ_RECOM,Return_1yr,Return_5yr,PE,LTD) values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)' |
使用循环语句和execute方法将我们的100条公司数据依次添加到数据库中,否则会报错
1 2 3 4 5 | >>>for company in company_data_list: try: cursor.execute(insert_statement,company) except: print("Inserting data error!{}".format(e)) |
要注意的是,执行完上面语句后我们的数据库并没有出现数据,因为我们并没有提交修改。这里使用commit方法提交
1 | >>>cnn.commit() |
然后关掉cursor,断开连接
1 2 | >>>cursor.close() >>>cnn.close() |
到这里我们就可以看到数据库中的内容了,回到mysql查看,
mysql> select * from sp100\G; *************************** 1. row *************************** Company_Name: Oracle Corp Ticker: ORCL Region: US SP_Sector: Information Technology SP_Sub_Industry: Systems Software Last_Closing_Price: 40.230 Ranking: 5 Star Capital_IQ_RECOM: N/A Return_1yr: 16.99 Return_5yr: 8.59 PE: 19.20 LTD: 43.76 *************************** 2. row *************************** ...
最后我们要对这些数据进行简单的处理并按照我们想要的方法分析
操作数据库
首先,举个例子(例子非真实情况):
假设我们要找到将各个公司的1年回报率乘以(1-长期债务率)得到公司在不负债的情况下对应的收益,然后加上它PE的二次方根。大致如下
Unlevered 1yr return = 1 yr return* (1-LTD%) weighted_index = unlevered return + SQRT(PE)
最后我们取前5名和后5名
我们来看怎么操作,之前我们讲到我们所有的数据都是字符串,字符串并不能正确地按数字大小排列内容。所以我们要先剔除带有所需数据中带有'N/A'的公司
mysql> create table sp as select * from sp100 where PE != 'N/A' and Return_1yr!='N/A' and LTD!='N/A';
这样一来我们就将PE,Return_1yr和LTD下带有‘N/A'的公司剔除了并放入一张新表sp中,接着我们将sp的这三列变成双精度浮点double类型
mysql> alter table sp change PE PE DOUBLE; mysql> alter table sp change Return_1yr Return_1yr DOUBLE; mysql> alter table sp change LTD LTD DOUBLE;
接着我们就可用通过select来查询我们需要的内容,我们要求数据库返回两列内容,一个是公司股票代码,另一个是weight.
这里weight=Return_1yr *((100-LTD)/100)+SQRT(PE),使用as将显示名称修改
查询weight最高的10只股票,'order by weight desc limit 10'表示,将结果按weight降序排列,显示5条
select ticker,Return_1yr *((100-LTD)/100)+SQRT(PE) as weight from sp order by weight desc limit 5;
结果如下
+--------+--------------------+ | ticker | weight | +--------+--------------------+ | NVDA | 325.56 | | NFLX | 189.14999999999998 | | AMZN | 121.97 | | PCLN | 51.97 | | ADBE | 51.89 | +--------+--------------------+
接着是升序排列找到最低的5条
select ticker,Return_1yr *((100-LTD)/100)+SQRT(PE) as weight from sp order by weight asc limit 5;
结果如下
+--------+---------------------+ | ticker | weight | +--------+---------------------+ | AGN | -14.370000000000001 | | GILD | -5.79 | | CVS | -1.08 | | NKE | 3.3999999999999986 | | PM | 3.6100000000000003 | +--------+---------------------+
好了,就这样。