第2章
金融数据挖掘之爬虫技术基础
“工欲善其事,必先利其器。”在进行金融数据挖掘项目实战之前,先来学习数据挖掘的一个技术手段—爬虫技术。爬虫技术其实就是利用计算机程序模拟人来访问网页,同时将网页上的数据获取下来,为数据的进一步分析做准备。
2.1 爬虫技术基础1—网页结构基础
想要从网页上挖掘数据,首先得对网页结构有一些基本的了解。本节会由浅入深地为大家揭开日常访问的网页背后的知识点。
2.1.1 查看网页源代码—F12键
学习网络爬虫技术首先得有一个浏览器,这里推荐谷歌浏览器(官网下载地址为https://www.google.cn/chrome/)。当然用其他浏览器也可以,如火狐浏览器等,只要按F12键(有的计算机要同时按住左下角的Fn键)能弹出网页源代码即可。
下面以谷歌浏览器为例来演示F12键的强大作用。在谷歌浏览器中使用百度搜索“阿里巴巴”,然后按F12键,在网页下方弹出如下图所示的界面。
按F12键后弹出来的界面称为开发者工具,是进行网络数据挖掘的利器。对于爬虫技术来说,只需要会用下图所示的两个功能即可。
第一个是“选择”按钮,第二个是“Elements(元素)”选项卡。
1.“选择”按钮
单击“选择”按钮,会发现按钮变成蓝色,然后在上方的网页中移动鼠标,鼠标指针所指向的网页元素的颜色会发生变化,“Elements”选项卡里的内容也会随之改变。例如,指向搜索到的第一个链接的标题,显示效果如下图所示。
当“选择”按钮处于蓝色状态时,在上方的网页中单击第一个链接的标题,这时“选择”按钮变回灰色,而“Elements”选项卡里的内容也不再变动,我们就可以开始观察单击的标题对应的网页源代码的具体内容了。我们一般只关心里面的中文内容,如果没看到中文文本,单击下图所示的三角箭头,即可展开内容,看到中文文本。
2.“Elements”选项卡
“Elements”选项卡里面的内容可以理解为就是网页的源代码,最后爬虫爬到的内容大致就是这样。下面接着完成一些“神奇”的操作。
如下图所示,双击“阿里巴巴”文本,这几个字会变成可编辑状态。
把“阿里巴巴”改成“工作”,然后同样双击下面一行的“批发网站_上”文本,使它变成可编辑状态,删除其中的“批发网站”,可以看到上方网页中的链接标题也相应地改变,如下图所示。
还可以用同样的操作,修改页面上的其他信息,如股价等。
通过开发者工具,我们可以对网页的结构有一个初步的认识,并利用“选择”按钮和“Elements”选项卡观察我们想获取的内容在源代码中的文本格式及所在位置。
2.1.2 查看网页源代码—右键菜单
在网页上右击,然后在弹出的快捷菜单中选择“查看网页源代码”命令,如右图所示,也会弹出一个当前网页的源代码页面,该页面上的信息就是通过Python能爬取到的信息。利用鼠标滚轮上下滚动源代码页面,能看到很多内容。
实战中常将上述两种方法联合使用:通过F12键对网页结构进行初步了解,然后在网页上右击并选择“查看网页源代码”命令,查看所需内容在网页源代码的位置,或者通过快捷键Ctrl+F搜索需要的内容。
有时通过F12键能看到的内容,但是通过右键菜单查看网页源代码的方法却观察不到,这是因为有些网页是动态渲染出来的。例如,新浪财经的股票信息是动态变化的,股价内容自然不会固定地写在网页源代码中,通过F12键看到的代码则是经过渲染的,内容更加丰富和全面,而通过右键菜单获得的内容可能就是一个网页框架,没有具体的数据。因此,如果通过F12键看到的和通过右键菜单看到的内容不一样,可以参考本书8.2节中Selenium库的相关知识点获取F12键渲染过的内容。
2.1.3 网址构成及http与https协议
许多人理解的网址是“www.baidu.com”,但其实应该是“https://www.baidu.com”,它前面的“https://”称为https协议,是网址的固定构成形式,表明该网址某种程度上是安全的,有的网址前面则为“http://”。在Python里输入“www.baidu.com”是识别不了的,必须把“https://”加上才可识别,演示代码如下:
那么到底应该加“http://”还是“https://”呢?其实最简单的判断方法就是在浏览器中直接访问该网址,成功打开页面后,复制地址栏中的网址到Python代码中即可。
2.1.4 网页结构初步了解
网页源代码初看非常复杂,但了解网页基本结构后再看网页源代码会轻松很多,所以下面先对网页结构做初步了解。如下图所示,其结构很简单,就是一个大框套着一个中框,一个中框再套着一个小框,这样一层层嵌套。在网页源代码中这种层级关系通过缩进表现得很清晰。
上图中,如果在一行网页源代码前看到一个三角箭头,就表明它是个大框,里面还嵌套着其他框,单击箭头就可以展开,看到里面嵌套的其他框。文本内容一般都在最小的框里。
2.2 爬虫技术基础2—网页结构进阶
本节来进一步了解网页结构,并试着自己搭建一个网页,为之后学习网络数据挖掘做准备。
对于其中的代码了解即可,感兴趣的读者可以自己实践一下,对之后的学习会更有帮助。
2.2.1 HTML基础知识1—我的第一个网页
代码文件:2.2.1 我的第一个网页.html
HTML(HyperText Markup Language)是一种用于编写网页的标准标记语言,本小节主要介绍如何利用它来搭建网页。下面来实际动手操作一下。打开Windows系统自带的“记事本”,输入如下内容:
将输入的内容保存为一个文本文件(后缀名为.txt)并关闭“记事本”,将文件后缀名由原来的.txt改为.html,此时这个文本文件就变成了HTML文件,即网页文件。双击该HTML文件,即可在默认浏览器中打开,显示如右图所示的网页效果。
知识点
Notepad++代码编辑器
上面用“记事本”编写了一个本地网页,这里再推荐一款专业的代码编辑器—Notepad++,其作用和PyCharm类似,都是方便我们编写代码的。Notepad++的下载地址为:https://notepad-plus-plus.org/。下载并安装完毕后,右击刚才创建的HTML文件,在弹出的快捷菜单中选择“Edit with Notepad++”命令,便可在Notepad++中打开文件编写代码。
2.2.2 HTML基础知识2—基础结构
本小节来完善一下之前的网页,为学习后面的知识点做准备。用“记事本”或Notepad++打开刚才创建的HTML文件,修改代码如下:
然后按快捷键Ctrl+S保存,在浏览器中重新打开该HTML文件或刷新网页,效果如右图所示。
之前讲过,网页结构就是大框套着中框、中框套着小框的框架结构,如下图所示。
前两行的与是固定写法,作用是将代码声明为HTML文档。
2.2.3 HTML基础知识3—标题、段落、链接
代码文件:2.2.3 逐渐完善的网页.html
浏览器中的显示效果如下图所示。
在前面的代码基础上略作修改:
<!DOCTYPE html>
<html>
<body>
<h1>这是标题 1</h1>
<p>这是标题1下的段落。</p>
<h2>这是标题 2</h2>
<p>这是标题2下的段落。</p>
</body>
</html>
浏览器中的显示效果如下图所示。
在前面代码的基础上略作修改:
<!DOCTYPE html>
<html>
<body>
<h1>这是标题 1</h1>
<p>这是标题1下的段落。</p>
<h2>这是标题 2</h2>
<a href="https://www.baidu.com">这是带链接的内容</a>
</body>
</html>
浏览器中的显示效果如下图所示。
此时单击链接文字就可以访问百度首页了,该访问方式是页面直接跳转到百度首页,并覆盖原网页。如果想在一个新的标签页里打开百度首页,而不覆盖原网页,只要在原来的基础上加上target=_blank即可,代码如下:
<a href="http://www.baidu.com" target=_blank>这是百度首页的链接</a>
2.2.4 HTML基础知识4—区块
2.2.5 HTML基础知识5—类与id
1.类(class)
2.id
感兴趣的读者可以在谷歌浏览器中打开其他网页,然后按F12键打开开发者工具,观察一下网页源代码。多做这种练习,能够更好地理解网页的结构,对之后学习网络数据获取及挖掘将大有帮助。
2.3 初步实战—百度新闻源代码获取
百度新闻是一个非常重要的数据源,本节就先来获取百度新闻的源代码。如下图所示,百度新闻目前改版为“资讯”版块,直接在“资讯”里便可搜索新闻。
2.3.1 获取网页源代码
代码文件:2.3.1 获取百度新闻网页源代码.py
通过1.4.4小节介绍的Requests库来尝试获取百度新闻的网页源代码,代码如下:
import requests
url = 'https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=阿里巴巴'
res = requests.get(url).text
print(res)
获取到的源代码如下图所示。
可以看到并没有获取到真正的网页源代码,因为百度新闻网站只认可浏览器发送的访问请求,而不认可通过Python发送的访问请求。为了解决这个问题,需要设置requests.get()中的headers参数,以模拟浏览器的访问请求。headers参数提供的是网站访问者的信息,headers中的User-Agent(用户代理)表示是用什么浏览器访问的,其设置方式如下所示,User-Agent的获取方法稍后会讲解。
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
设置完headers之后,在通过requests.get()请求时需加上headers参数,这样就能模拟是在通过一个浏览器访问网站了,代码如下:
res = requests.get(url, headers=headers).text
完整代码如下所示:
import requests
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
url = 'https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=阿里巴巴'
res = requests.get(url, headers=headers).text
print(res)
运行结果如下图所示,可以发现此时已经获取到真正的网页源代码了。
这里的headers是一个字典,它的第一个元素的键名为'User-Agent',值为'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'。User-Agent实际上代表访问网站的浏览器是哪种浏览器。
上述代码用的是谷歌浏览器的User-Agent,下面就以谷歌浏览器为例讲解如何获取浏览器的User-Agent。打开谷歌浏览器,在地址栏中输入“about:version”,注意要用英文格式的冒号,按Enter键后在打开的界面中找到“用户代理”项,后面的字符串就是User-Agent。
对于之后的项目实战,只要记得在代码的最前面写上如下代码:
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
然后每次用requests.get()访问网站时,加上headers=headers即可。
res = requests.get(url, headers=headers).text
有时不加headers也能获得网页的源代码,如第1章通过Requests库爬取Python官网就不需要加headers。不过headers只需在代码的开头设置一次,之后直接在requests.get()中添加headers=headers即可,所以并不麻烦,而且可以避免可能会出现的爬取失败。
通过短短几行代码,就能够获得网页的源代码,这是网络数据挖掘中最重要的一步,之后所需要做的工作就是信息提取和分析了。
2.3.2 分析网页源代码信息
获取到网页源代码后,还需要提取其中的新闻标题、网址、来源和日期等信息。在提取信息之前,可通过三种常见的分析方法来观察这些信息的特征,这三种方法经常结合使用。
1.利用浏览器的F12键
利用2.1.1小节所讲的方法,在谷歌浏览器中打开百度新闻后按F12键,调出开发者工具,然后单击“选择”按钮,选择一个新闻标题,可以在“Elements”选项卡中看到该标题内容,用同样的方法可以查看新闻来源和日期等信息。如果看不到其中的中文文本,单击三角箭头展开代码,就可以看到了。
2.利用浏览器的右键快捷菜单
利用2.1.2小节所讲的方法,在打开的网页中右击,在弹出的快捷菜单中选择“查看网页源代码”命令,切换到源代码页面后,滚动鼠标滚轮可查看更多内容,按快捷键Ctrl+F可快速搜索和定位所需内容。
3.利用Python的输出框
在获取到网页源代码的输出框内按快捷键Ctrl+F,调出搜索框,搜索所需内容,如下图所示。这种方法也比较常用,不过需要先通过程序获得网页源代码。
在网页源代码里,我们需要的信息通常会被一些英文、空格及换行包围着,需要通过一些手段提取出来。一个常用的手段就是使用正则表达式,将在下一节进行详细讲解。
2.4 爬虫技术基础3—正则表达式
正则表达式是非常好用的信息提取工具,它可以灵活、高效地提取文本中的所需信息。
2.4.1 正则表达式基础1—findall()函数
代码文件:2.4.1 正则表达式之findall.py
首先通过一个实例来演示正则表达式的用法。例如,想提取字符串'Hello 123 world'中的3个数字,可以通过如下代码实现:
import re
content = 'Hello 123 world'
result = re.findall('\d\d\d', content)
print(result)
上述代码的第1行用于引用正则表达式re库,re库是Python自带的,不用单独安装。
下面来讲解findall()函数的使用规则。findall()函数的功能是在原始文本中寻找所有符合匹配规则的文本内容,其使用格式如下所示:
re.findall(匹配规则, 原始文本)
匹配规则是一个由特定符号组成的字符串。上述第3行代码的findall()函数使用的匹配规则中,'d'就是一个特定符号,表示匹配1个数字,那么'ddd'就表示匹配3个数字,所以re.findall('ddd', content)就是在content中寻找连续的3个数字。
程序运行结果如下,得到的是一个包含提取出的信息的列表。
['123']
对于初学者来说要注意的一点是,findall()函数得到的是一个列表,而不是字符串或数字。下面再通过一个实例加深印象,这里需要找到字符串里所有的3位数字,代码如下:
import re
content = 'Hello 123 world 456 华小智Python基础教学135'
result = re.findall('\d\d\d', content)
print(result)
程序运行结果如下:
['123', '456', '135']
如果想获取得到的列表中的某个元素,就需要使用1.2.3小节里介绍过的list[i]方法,代码如下:
a = result[0] # 注意列表的第1个元素的序号是0
通过print()函数将变量a打印输出,结果如下:
123
虽然上述输出结果看着是数字,但它是从字符串里提取出来的,所以其实是3个字符,而不是数字,在如下列表里也可以看出。
['123', '456', '135']
有的读者可能会问,如果除了匹配数字还想匹配文字,应该怎么书写匹配规则?如果原始文本包含空格或换行符,又该怎么办呢?实际上,像'd'这样的特定符号还有很多,下表中列出了一些常用的特定符号及其功能。
将这些符号组合起来,就能得到千变万化的匹配规则。不过在金融数据挖掘与分析实战中,大部分情况下需要用到的只有两种:(.?)与.?。接下来会分为两个小节进行讲解。
2.4.2 正则表达式基础2—非贪婪匹配之(.*?)
代码文件:2.4.2 正则表达式之非贪婪匹配1.py
通过上一小节的学习,我们已经知道,“.”表示除了换行符外的任意字符,“”表示0个或多个表达式。将“.”和“”合在一起组成的匹配规则“.*”称为贪婪匹配。之所以叫贪
婪匹配,是因为会匹配到过多的内容。如果再加上一个“?”构成“.*?”,就变成了非贪婪匹配,它能较精确地匹配到想要的内容。对于贪婪匹配和非贪婪匹配的概念了解即可,在实战中通常都是用非贪婪匹配。
非贪婪匹配除了.?这种形式外,还有一种形式是(.?)。本小节先介绍(.?),下一小节则介绍.?。
简单来说,(.*?)用于获取文本A与文本B之间的内容,并不需要知道它的确切长度及格式,但是需要知道它在哪两个内容之间,其使用格式如下所示:
文本A(.*?)文本B
下面结合findall()函数和非贪婪匹配(.*?)进行文本提取的演示,代码如下:
import re
res = '文本A百度新闻文本B'
source = re.findall('文本A(.*?)文本B', res)
print(source)
程序运行结果如下:
['百度新闻']
在实战中,一般不把匹配规则直接写到findall后面的括号里,而是拆成两行来写,如下所示,先写匹配规则,再写findall()语句。原因是有时匹配规则较长,分开写会比较清晰。
p_source = '文本A(.*?)文本B'
source = re.findall(p_source, res)
在实战中,符合'文本A(.*?)文本B'匹配规则的内容通常不止一个,下面再来看一段演示代码:
import re
res = '文本A百度新闻文本B,新闻标题文本A新浪财经文本B,文本A搜狗新闻文本B新闻网址'
p_source = '文本A(.*?)文本B'
source = re.findall(p_source, res)
print(source)
上述代码可获得所有符合'文本A(.*?)文本B'匹配规则的内容列表,运行结果如下:
['百度新闻', '新浪财经', '搜狗新闻']
以2.3.1小节中获取到的百度新闻网页源代码为例,如下图所示,现在需要从中提取新闻来源及日期信息。
import re
res = '<p class="c-author"><img***>央视网新闻 2019年04月13日 13:33</p>'
p_info = '<p class="c-author">(.*?)</p>'
info = re.findall(p_info, res)
print(info)
这里为了方便演示,将所有的网页源代码内容都写到一行里,实战中其实是有换行的,关于换行的处理将在2.4.4小节进行讲解。程序运行结果如下:
['*>央视网新闻 2019年04月13日 13:33']
提取的内容中有一些我们不需要的文本,如*>图片标签及 ,可以通过文本清洗去除,具体方法将在2.4.5小节及第3章进行讲解。
2.4.3 正则表达式基础3—非贪婪匹配之.*?
代码文件:2.4.3 正则表达式之非贪婪匹配2.py
(.?)用于获取文本A与文本B之间的内容,那么.?是用来做什么的呢?简单来说,.?用于代替文本C和文本D之间的所有内容。之所以要使用.?,是因为文本C和文本D之间的内容经常变动或没有规律,无法写到匹配规则里;或者文本C和文本D之间的内容较多,我们不想写到匹配规则里。.*?的使用格式如下所示:
文本C.*?文本D
下面举例介绍.*?的作用,演示代码如下:
import re
res = '<h3>文本C<变化的网址>文本D新闻标题</h3>'
p_title = '<h3>文本C.*?文本D(.*?)</h3>'
title = re.findall(p_title, res)
print(title)
上述代码中,文本C和文本D之间为变化的网址,用.?代表,需要提取的是文本D和之间的内容,用(.?)代表,运行结果如下:
['新闻标题']
下面利用上图中的网页源代码做一个获取新闻标题的示范。为方便演示,先不考虑换行的问题,把网页源代码写到一行里,其中data-click="{}"的大括号中的一些英文和数字内容简化为“英文&数字”。演示代码如下:
import re
res = '<h3 class="c-title"><a href="网址" data-click="{英文&数字}"><em>阿里巴巴</em>代码竞赛现全球首位AI评委 能为代码质量打分</a>'
p_title = '<h3 class="c-title">.*?>(.*?)</a>'
title = re.findall(p_title, res)
print(title)
运行结果如下:
['阿里巴巴代码竞赛现全球首位AI评委 能为代码质量打分']
上述演示代码的核心是下面这行代码,下面会讲解这行代码是如何写出来的。
p_title = '<h3 class="c-title">.*?>(.*?)</a>'
先来分析下图中的网页源代码,可以看到图中框出的几处是用来定位新闻标题的:
根据上图,书写出如下图所示的匹配规则,图中做了标注,方便大家理解。
p_href = '<h3 class="c-title"><a href="(.*?)"'
href = re.findall(p_href, res)
以上的演示代码都没有考虑换行的情况,但实际的网页源代码里存在很多换行,而(.?)和.?无法自动匹配换行,如果遇到换行就不会继续匹配换行之后的内容了。此时就要用到下一小节的知识点:修饰符re.S。
2.4.4 正则表达式基础4—自动考虑换行的修饰符re.S
代码文件:2.4.4 正则表达式之换行.py
修饰符有很多,最常用的是re.S,其作用是在使用findall()查找时,可以自动考虑到换行的影响,使得.*?可以匹配换行。使用格式如下:
re.findall(匹配规则, 原始文本, re.S)
演示代码如下:
import re
res = '''文本A
百度新闻文本B'''
p_source = '文本A(.*?)文本B'
source = re.findall(p_source, res, re.S)
print(source)
由于文本A和文本B之间有换行,如果在findall后的括号中不写re.S,则获取不到内容,因为(.*?)匹配不了换行。之前讲过3个单引号'''一般用来写注释,而这里是用来将带有换行的文本框起来。运行结果如下:
['百度新闻']
下面在2.3.1小节中获取到的百度新闻网页源代码中提取新闻标题和链接,代码如下:
import re
res = '''<h3 class="c-title">
<a href="https://baijiahao.baidu.com/s?id=1631161702623128831& wfr=spider&for=pc"
data-click="{
英文&数字
}"
target="_blank"
>
<em>阿里巴巴</em>代码竞赛现全球首位AI评委 能为代码质量打分
</a>
'''
p_href = '<h3 class="c-title">.*?<a href="(.*?)"'
p_title = '<h3 class="c-title">.*?>(.*?)</a>'
href = re.findall(p_href, res, re.S)
title = re.findall(p_title, res, re.S)
href列表和title列表打印输出结果如下:
['https://baijiahao.baidu.com/s?id=1631161702623128831&wfr=spider&for=pc']
['\n <em>阿里巴巴</em>代码竞赛现全球首位AI评委 能为代码质量打分\n ']
获取的新闻标题包含换行符n和空格,可以利用1.4.3小节介绍的strip()函数清除,代码如下。虽然title列表里只有一个元素,还是通过for循环语句来批量处理,在下一章将有应用。
for i in range(len(title)):
title[i] = title[i].strip()
清洗过的title列表如下,其中还有等无效内容,下一小节将讲解如何进一步清洗文本。
['阿里巴巴代码竞赛现全球首位AI评委 能为代码质量打分']
2.4.5 正则表达式基础5—知识点补充
代码文件:2.4.5 正则表达式之知识点补充.py
1.sub()函数
sub()函数中的sub是英文substitute(替换)的缩写,其格式为:re.sub(需要替换的内容,替换值,原字符串)。该函数主要用于清洗正则表达式获取到的内容,如之前获取到的有无效内容的新闻标题:
['阿里巴巴代码竞赛现全球首位AI评委 能为代码质量打分']
其中的和并不是我们需要的内容,可以通过如下代码将其替换为空值,即将其删除。注意代码中的title是一个列表,虽然只有1个元素,但也要用title[0]才能获得其中的字符串,然后才可以使用sub()函数进行清洗。
title = ['阿里巴巴代码竞赛现全球首位AI评委 能为代码质量打分']
title[0] = re.sub('', '', title[0])
title[0] = re.sub('', '', title[0])
print(title[0])
上述代码采用的替换方式类似于1.4.3小节提到的replace()函数,即依次替换特定的字符串。这种方式的缺点是,得为每个要替换的字符串写一行替换代码,如果要替换的字符串有很多,工作量就会比较大。此时可以观察要替换的字符串,如果它们有类似的格式,就可以用sub()函数通过正则表达式进行批量替换。采用正则表达式进行替换的代码如下:
import re
title = ['<em>阿里巴巴</em>代码竞赛现全球首位AI评委 能为代码质量打分']
title[0] = re.sub('<.*?>', '', title[0])
print(title[0])
上述两种替换方式的代码都可以得到如下运行结果:
阿里巴巴代码竞赛现全球首位AI评委 能为代码质量打分
title[0] = re.sub('<.?>', '', title[0])中的<.?>可能不太容易理解。回顾之前所讲的内容,.?用于代表文本C和文本D之间的所有内容,所以<.?>就表示任何<×××>形式的内容,如右图所示,其中自然包括和。
2.中括号[ ]的用法
中括号最主要的功能是使中括号里的内容不再有特殊含义。在正则表达式里,“.”“”“?”等符号都有特殊的含义,但是如果想定位的就是这些符号,就需要使用中括号。例如,想把字符串里所有的“”号都替换成空值,演示代码如下:
company = '*华能信托'
company1 = re.sub('[*]', '', company)
print(company1)
运行结果如下。这在爬取股票名称时很有用,因为有时上市公司名称里有*号需要替换掉。
华能信托