使用Selenium驱动浏览器的爬虫

我们这次使用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

来看看我们要抓的数据是什么样子的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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

除第一行

1
<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

1
2
>>>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里面所有匹配的内容

1
>>>company_data_list = re.findall(pattern,source)

得到的内容如下

1
2
3
4
>>>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 &amp; Direct Marketing Retail', '830.380', '4 Star', 'N/A', '43.27', '33.73', '190.00'),
...]

好了我们的爬取工作已经完成,接下来我们写入MySQL数据库

写入数据库

首先我们可以登入我们的MySQL

1
2
3
    $ 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>
1
接着我们要创建一个数据库,名字叫test,字符编码UTF-8
mysql> CREATE DATABASE test charset = 'utf8';
1
2

接着进入我们的数据库
mysql> USE test;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
创建一张表,名字叫sp100,同时包含了表格各列的名称和数据类型,由于我们抓取的内容都是字符串,并且有的内容标示为'N/A',我们不能使用数字格式,这里使用字符串格式;

```SQL
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 |
+--------+---------------------+

好了,就这样。