带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1

点击查看第一章
点击查看第二章

第3章

金融数据挖掘案例实战1

学完正则表达式就可以进行比较高阶的操作了。前面介绍了如何获取百度新闻的网页源代码,本章接着利用正则表达式进行信息提取和文本分析,完成百度新闻的数据挖掘。此外,本章还将进行搜狗新闻、新浪财经的数据挖掘,帮助大家更好地掌握网络数据挖掘的方法。

3.1 提取百度新闻标题、网址、日期及来源

代码文件:3.1 百度新闻数据挖掘.py
2.3节已经实现了百度新闻网页源代码的获取,下面通过正则表达式提取百度新闻标题、网址、日期及来源等信息。

3.1.1 获取网页源代码

首先回顾一下2.3节用于获取在百度新闻中搜索“阿里巴巴”的网页源代码的代码:

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)

3.1.2 编写正则表达式提取新闻信息

1.提取新闻的来源和日期
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1

import re
p_info = '<p class="c-author">(.*?)</p>'
info = re.findall(p_info, res, re.S)
print(info)

这里再强调一次,网页源代码里存在换行,而带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1无法匹配换行,因此,在使用findall()函数时要给出re.S参数,在查找时才会自动考虑换行,否则会提取不到内容。第3行和第4行代码中的info是一个存储findall()函数查找到的内容的列表,也可以换成其他名字,此处用info是因为它是information(信息)的缩写。
最后输出的内容如下:
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
可以看到获得的新闻来源和日期是混杂在一起的,而且夹杂着很多标签、空格、换行符n和制表符t等,所以还需要对数据进行二次提炼,这个过程被称为数据清洗,在3.1.3小节会进行详细介绍,这里先接着讲如何提取新闻的网址和标题。
2.提取新闻的网址和标题
提取新闻的网址和标题的方法已经在2.4.3、2.4.4小节讲过了,这里当作复习并补充一些新内容。先来看看如何寻找新闻网址的提取规律,为编写相应的正则表达式做准备。
除了在Python获取到的网页源代码里寻找规律,还可以通过F12键查看网页源代码并寻找规律。如下图所示,新闻网址前面都有带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1,这个title就是“标题”的意思,而且2.2.5小节提过,class表示类别,同类型数据的class相同,所以猜测之后用正则表达式
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1来定位新闻网址时很有可能就会用到它。
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
下面来看看具体的代码:

p_href = '<h3 class="c-title">.*?<a href="(.*?)"'
href = re.findall(p_href, res, re.S)

和在2.4.3小节里提到的一样,在用来定位的文本A中,用.*?代替不关心的内容,对于文本B,因为网址后面会接着一个双引号,所以可以用该双引号作为定位条件。
提取标题的正则表达式编写在2.4.3小节也讲过,代码如下:

p_title = '<h3 class="c-title">.*?>(.*?)</a>'
title = re.findall(p_title, res, re.S)

简单复习一下p_title是怎么写出来的。如下图所示,其中带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1到下一个>号之间我们不关心的内容,里面含有新闻的网址和其他内容。第2个>号之后就是新闻的标题,利用带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1来提取该内容。最后用来定位的文本B,选择的是,因为观察网页源代码会发现标题都会以作为收尾。
通过print(href)和print(title)将两个列表打印输出,输出的内容如下:
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
可以看到提取出的网址基本没有问题,而提取出的标题并不完善,每个标题的首尾含有换行符n和一些空格,标题的中间则含有等无效字符,此时就需要对数据进行清洗,下一小节将进行详细介绍。

3.1.3 数据清洗并打印输出

本小节的内容比较重要,因为之后很多数据清洗都是基于这里讲到的一些处理手段。上一小节提取到的数据除了新闻的网址基本没有问题外,新闻的标题、日期和来源并不完善。下面依次进行数据清洗,先从相对容易的新闻标题开始。
1.新闻标题清洗
提取到的新闻标题数据存在两个问题:一是每个标题的首尾含有换行符n和一些空格;二是中间含有等无效字符。首先用strip()函数把不需要的空格和换行符去掉,代码如下:

for i in range(len(title)):
    title[i] = title[i].strip()

接着用2.4.5小节讲过的sub()函数处理,代码如下:

for i in range(len(title)):
    title[i] = title[i].strip()
    title[i] = re.sub('<.*?>','',title[i])

带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
2.新闻来源和日期清洗
现在对新闻来源和日期进行数据清洗。3.1.2小节中提到过,提取到的info列表的主要问题有:夹杂着很多标签信息;来源和日期连在一起;来源和日期的首尾都有一些空格和换行符等内容。
首先清洗标签信息,代码如下:

info[i] = re.sub('<.*?>', '', info[i])

然后需要分离来源和日期。先观察info列表中的元素,如下图所示。
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
从上图中可以发现,来源和日期都被如下所示的字符串隔开了:
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
那么就可以用1.4.3小节讲过的split()函数分割info列表的每一个元素中的新闻来源和日期,代码如下:
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
需要注意的是,通过split()函数分割得到的是一个列表,之后需要通过在列表中选取元素的方式分别提取具体的来源和日期。
最后,使用strip()函数去除多余的空格和换行符,代码如下:

source[i] = source[i].strip()
date[i] = date[i].strip()

清洗新闻来源和日期的完整代码如下:

source = []  # 先创建两个空列表,分别用于存储分割后的来源和日期
date = []  

for i in range(len(info)):
    info[i] = re.sub('<.*?>', '', info[i])
    source.append(info[i].split('  ')[0])  
    date.append(info[i].split('  ')[1])
    source[i] = source[i].strip()
    date[i] = date[i].strip()  

上述代码使用1.2.3小节讲过的append()函数对分割后的来源和日期数据进行整理。具体操作为:先创建两个空列表,为存储清洗结果做准备,然后用append()函数为两个空列表添加新元素(新的来源和日期)。程序运行后,source列表和date列表的打印输出结果如下图所示,可以看到来源和日期都被较好地提炼出来了。
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
所有代码汇总如下:

import requests
import re
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

# 正则提取
p_href = '<h3 class="c-title">.*?<a href="(.*?)"'
p_title = '<h3 class="c-title">.*?>(.*?)</a>'
p_info = '<p class="c-author">(.*?)</p>'
href = re.findall(p_href, res, re.S)
title = re.findall(p_title, res, re.S)
info = re.findall(p_info, res, re.S)

# 数据清洗及打印输出
source = []
date = []

for i in range(len(title)):
    title[i] = title[i].strip()
    title[i] = re.sub('<.*?>', '', title[i])
    info[i] = re.sub('<.*?>', '', info[i])
    source.append(info[i].split('  ')[0])  
    date.append(info[i].split('  ')[1])
    source[i] = source[i].strip()
    date[i] = date[i].strip()  
    
    print(str(i+1) + '.' + title[i] + '(' + date[i] + '-' + source[i] + ')')  
    print(href[i])  

其中,第27行代码需要注意两点:第一,i是数字,所以字符串拼接时要用str()函数进行转换;第二,i是从0开始的,所以要写成str(i+1)。
运行结果如下图所示,所需要的信息基本都提取到了。
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1

3.2 批量获取多家公司的百度新闻并生成数据报告

上一节实现了从百度新闻获取一家公司的新闻标题、网址、来源和日期信息,本节来实现批量获取多家公司的信息,并自动生成数据报告,导出为一个文本文件。

3.2.1 批量爬取多家公司的百度新闻

代码文件:3.2.1 批量爬取多家公司的百度新闻.py
完成阿里巴巴新闻舆情的数据挖掘后,如果要做另一家公司的新闻舆情数据挖掘该怎么办呢?最简单的办法就是复制针对阿里巴巴编写的代码,再修改url。但是如果有几十家公司的新闻舆情需要爬取,采用复制代码后修改url的方法就显得有点低效,此时可以利用自定义函数来完成批量爬取。
首先带大家回顾自定义函数的一些基础知识,代码如下:

def baidu(company):
    url = 'https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=' + company 
    print(url)
    
# 批量调用函数
companys = ['华能信托','阿里巴巴','百度集团']
for i in companys: 
    baidu(i)

首先定义了一个名为baidu的函数,这个函数的内容只有2行代码:第1行创建了名为url的变量并赋值,其中company是函数参数;第2行把url打印输出。然后通过for循环语句批量调用定义的baidu()函数。
程序运行结果如下:

https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=华能信托
https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=阿里巴巴
https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=百度集团

这部分就是之后要讲到的批量爬取的核心内容,修改url以实现多家公司数据的批量爬取,代码如下:

# 此处省略的是引用库和设置headers的代码,详见代码文件
def baidu(company):
    url = 'https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=' + company 
    res = requests.get(url, headers=headers).text
    # 此处省略的是3.1节中的数据提取、清洗代码,注意缩进,详见代码文件

companys = ['华能信托','阿里巴巴','万科集团','百度集团','腾讯','京东']
for i in companys:
    baidu(i)
    print(i + '百度新闻爬取成功')

3.2.2 自动生成舆情数据报告文本文件

代码文件:3.2.2 自动生成舆情数据报告文本文件.py
现在我们已经能够批量爬取新闻并生成舆情结果,如果想把这些内容保存到一个文件中,
该怎么办呢?最简单的方法就是复制粘贴到文本文件或Word文档中,不过这个方法比较低效。下面介绍如何将爬取到的内容自动导出为文本文件。
先通过一个简单的示例学习如何通过Python生成一个文本文件,代码如下:

file1 = open('E:\\测试.txt', 'a')
file1.write('把内容输入到txt中,就是这么简单')
file1.close()

运行代码,会发现在E盘中多出一个名为“测试.txt”的文件,里面的文本内容为“把内容输入到txt中,就是这么简单”。下面分别解释各行代码的作用。
第1行代码利用open()函数打开一个文件,该函数的使用格式如下:
变量名 = open('文件路径','打开文件的模式')
文件路径就是文本文件所在的地址,可写成绝对路径或相对路径,文件路径的相关知识见本小节最后的“知识点”。对于新建文本文件而言,打开文件有两种常用的模式,见下表。在这两种模式下,如果文件路径中指定的文本文件不存在,就会自动新建一个文本文件。
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
这里采用追加模式a,这样就不用担心原来的数据被清除。第2行代码使用write()函数把字符串写入文本文件中。读者可以试着更换括号中的字符串,看看会得到怎样的运行结果。
最后使用close()函数关闭文件。虽然有时不用close()函数也能生成文件,但通过open()函数打开文件后再用close()函数关闭文件有助于释放缓存,是一个良好的编程习惯。
掌握了如何创建文本文件及如何写入内容之后,就可以进行自动生成数据报告的实战了,代码如下:

# 此处省略的是引用库和设置headers的代码,详见代码文件
def baidu(company):
    url = 'https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=' + company 
    res = requests.get(url, headers=headers).text
    # 此处省略的是3.1节中的数据提取、清洗代码,注意缩进,详见代码文件    

    file1 = open('E:\\数据挖掘报告.txt', 'a')  # 注意缩进的细节,不要写到上面数据清洗的for循环里去
    file1.write(company + '数据挖掘completed!' + '\n' + '\n')

    for i in range(len(title)):
        file1.write(str(i + 1) + '.' + title[i] + '(' + date[i] + '-' + source[i] + ')' + '\n')
        file1.write(href[i] + '\n')  # '\n'表示换行
    file1.write('———————————————————' + '\n' + '\n')
    file1.close()

companys = ['华能信托','阿里巴巴','万科集团','百度集团','腾讯','京东']
for i in companys:
    baidu(i)
    print(i + '百度新闻爬取成功')

上述代码中多处使用了n,之前讲正则表达式时简单提到过,n表示换行符,这里把它当成字符串,和报告的内容拼接在一起,实现在恰当的地方换行的效果,让报告的版面更美观。现在运行程序,看看E盘是不是多了一个“数据挖掘报告.txt”文件呢?在第13章还会讲解通过Python生成Word文档格式的报告,感兴趣的读者可先行阅读。
知识点
文件路径
书写文件路径时通常写两个反斜杠“\”,这是因为单个反斜杠在Python里有特殊意义,例如,n表示换行符。用两个反斜杠“\”可以取消单个反斜杠的特殊含义。例如,E盘“舆情报告”文件夹下的“数据挖掘报告.txt”文件,其文件路径就要写成“E:\舆情报告\数据挖掘报告.txt”。虽然这里面并没有类似n的特殊内容,也可以直接写成“E:舆情报告数据挖掘报告.txt”,但写文件路径时用两个反斜杠可以避免失误,是一个好的编程习惯。
除了用两个反斜杠来取消单个反斜杠的特殊意义外,在文件路径的字符串前面加一个“r”,也可以取消单个反斜杠的特殊意义,演示代码如下:
file1 = open(r'E:舆情报告数据挖掘报告.txt', 'a')
上面的文件路径形式叫绝对路径,还有一种文件路径形式叫相对路径,即只写一个文件名,如“数据挖掘报告.txt”,生成的文件会存储在代码文件所在的文件夹中。

3.3 异常处理及24小时实时数据挖掘实战

现在我们已经可以获取多家公司的信息。在获取过程中,通常不希望因为偶尔的程序异常导致整个程序停止运行,所以本节就来介绍异常处理的知识。另外,在商业实战中,信息的获取往往是24小时不间断进行的,本节也将讲解如何达到这一目的。
代码文件:3.3 异常处理及24小时实时数据挖掘实战.py

3.3.1 异常处理实战

有时虽然程序已经写得比较完善,但是在实战中总会遇到一些意外,例如,网页结构可能会出现预想不到的情况,网络有可能突发故障导致访问网站失败,等等。为了防止程序因为偶然的异常而终止运行,就要用到1.3.4节所讲的try/except异常处理语句,代码如下:

companys = ['华能信托','阿里巴巴','万科集团','百度','腾讯','京东']
for i in companys:
    try:
        baidu(i)
        print(i + '百度新闻爬取成功')
    except:
        print(i + '百度新闻爬取失败')

如果baidu()函数出现异常,例如,在爬取“万科集团”的信息时出现了一个异常的网页结构,就不会因为程序异常而终止整个程序的运行,而会执行except后的操作,打印输出“万科集团百度新闻爬取失败”。

3.3.2 24小时实时爬取实战

现在已经可以进行批量爬取并通过异常处理来避免程序中断,倘若要24小时不间断地进行实时爬取,就要用1.3.3小节中介绍的while语句构造一个永久循环。
只要添加如下所示的一行while True代码,便可以让程序一直执行:
while True:
具体代码如下:

while True:
    companys = ['华能信托','阿里巴巴','万科集团','百度','腾讯','京东']
    for i in companys:
        try:
            baidu(i)
            print(i + '百度新闻爬取成功')
        except:
            print(i + '百度新闻爬取失败')

这样程序就会24小时不间断地运行下去,而且因为加上了异常处理语句,就算出现异常信息,整个程序的运行也不会中断。
如果不需要不间断地运行,而是每隔一定时间运行一次,可以引用time库,然后使用time.sleep()函数来达到目的,代码如下:

import time
while True:
    companys = ['华能信托','阿里巴巴','万科集团','百度','腾讯','京东']
    for i in companys:
        try:
            baidu(i)
            print(i + '百度新闻爬取成功')
        except:
            print(i + '百度新闻爬取失败')
    time.sleep(10800)

第1行代码中用import语句引用time库。第10行代码中time.sleep()括号里的数字单位是秒,所以time.sleep(10800)的意思就是休息10800秒,也就是休息3小时。这样for循环执行完一遍之后,就会自动休息3小时再执行。注意第10行代码不要写到for循环里去,它要和第4行for语句的缩进量相同,因为是要等for循环执行完了,才执行time.sleep()操作。
完整代码如下所示:

import requests
import re
import time

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'}

def baidu(company):
    url = 'https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=' + company 
    res = requests.get(url, headers=headers).text
    # 此处省略的是3.1节中的数据提取、清洗代码,注意缩进,详见代码文件


while True:
    companys = ['华能信托','阿里巴巴','万科集团','百度','腾讯','京东']
    for i in companys: 
        try:
            baidu(i)
            print(i + '百度新闻爬取成功')
        except:
            print(i + '百度新闻爬取失败')
    time.sleep(10800)

至此便实现了24小时不间断地爬取多家公司的新闻数据。有的读者可能会有这样的疑问:这里只爬取了百度新闻的第一页,会错过其他重要新闻吗?答案是不会的,因为这是24小时不间断爬取,所以只要出现新的新闻就可以捕捉到。有的读者可能又有疑问:这样一直爬取,会不会爬取到很多重复的新闻呢?答案是会的,不间断爬取的确会爬取到重复的内容,而如何进行数据去重,则需要用到数据库的相关知识,将在5.1.1小节进行详细讲解。

3.4 按时间顺序爬取及批量爬取多页内容

这一节介绍如何按照时间顺序爬取百度新闻信息,以及如何一次性批量爬取百度新闻的多页内容。

3.4.1 按时间顺序爬取百度新闻

代码文件:3.4.1 按时间顺序爬取百度新闻.py
之前爬取的百度新闻并不是按照时间排序的,这是因为百度新闻默认以“按焦点排序”的方式显示搜索结果,也就是把热点新闻放在最前面,如下图所示。
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
如果想按照时间顺序来获取新闻,只需要在上图中圈出的下拉列表框里选择“按时间排序”,效果如下图所示。
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
单击“确认”按钮,记住该选择,之后打开百度新闻,默认就是按时间排序了。
此时按时间排序的网址为:

https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=阿里巴巴

之前按焦点排序的网址为:

https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=阿里巴巴

可以看到,它们唯一的区别就在于中间的“rtt=”之后的数字,我们可以推断出:“rtt=4”表示按时间排序,“rtt=1”表示按焦点排序。那么如果要写按时间顺序爬取百度新闻的代码就很容易了,只要把之前代码里的url中的“rtt=1”改成“rtt=4”即可。如下所示:

def baidu(company):
    url = 'https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=' + company

3.4.2 一次性批量爬取多页内容

3.3节中实现24小时爬取后,虽然只爬取第一页,但是24小时都在爬取,并不会遗漏信息,其实已经不太需要爬取多页。如果有的读者还是希望能批量爬取多页,可以学习下面的知识。
1.批量爬取一家公司的多页信息
代码文件:3.4.2-1 批量爬取一家公司的多页信息.py
其实批量爬取多页和按时间顺序爬取比较类似,都是在网址上做文章。先来看一下用百度新闻搜索“阿里巴巴”得到的第一页内容的网址:

https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=阿里巴巴

在第一页底部单击链接跳转到第二页,网址如下(其中删除了不影响链接跳转的内容):

https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=阿里巴巴&pn=10

此时好像还找不出什么规律,让我们再看看第三页,网址如下:

https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=阿里巴巴&pn=20

可以再多翻几页,对比一下网址,就能发现一个规律:从第二页开始,每个网址只有一个内容有变化,即“&pn=××”,其等号后的数字呈现10、20、30……的递增规律,可以推断这个就是爬取多页的关键(猜测pn可能是page number的缩写)。有读者会问:第一页的网址中并没有“&pn=××”的内容呀?其实在网址中,有些内容不是必需的,我们可以删除一些内容来试试看。网址中的参数通常通过&符号连接,所以删除参数也是根据&符号进行。例如,上面的网址如果删去“&bsst=1”或“&cl=2”,仍然能够访问。
回到第一页的网址的问题,按照前面发现的递增规律,猜测第一页的网址参数应该是“&pn=0”,可以构造出第一页的网址如下:

https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=阿里巴巴&pn=0

将这个网址复制、粘贴到浏览器的地址栏中并打开,可以看到的确是第一页的内容,说明上述规律是有效的。下面就可以根据这个规律构造网址,批量爬取多页内容了,代码如下:

def baidu(page):
    num = (page - 1) * 10 
    url = 'https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=阿里巴巴&pn=' + str(num) 
    res = requests.get(url, headers=headers).text
    # 此处省略的是3.1节中的数据提取、清洗代码,注意缩进,详见代码文件
for i in range(10): # i是从0开始的序号,所以下面要写成i+1
    baidu(i+1)
    print('第' + str(i+1) + '页爬取成功')

注意其中的num、i是数字类型的变量,所以在做字符串拼接时要用str()函数进行转换。
2.批量爬取多家公司的多页信息
代码文件:3.4.2-2 批量爬取多家公司的多页信息.py
上面实现的是批量爬取一家公司的多页内容,如果想批量爬取多家公司的多页内容,可以给函数设置两个参数,通过两个for循环批量爬取,代码如下:

def baidu(company, page):
    num = (page - 1) * 10
    url = 'https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=' + company + '&pn=' + str(num)
    res = requests.get(url, headers=headers).text
    # 此处省略的是3.1节中的数据提取、清洗代码,注意缩进,详见代码文件
    
companys = ['华能信托','阿里巴巴','万科集团','百度集团','腾讯','京东']
for company in companys:
    for i in range(20): 
        baidu(company, i+1)
        print(company + '第' + str(i+1) + '页爬取成功')

知识点
访问超时设置—timeout参数的使用
有时访问一个网址,可能等待很久都没有反应,由于无法获得网页源代码,程序就会一直等待,呈现“假死”状态。为避免陷入无限的等待之中,需要设置访问超时,也就是说,如果访问一个网址的等待响应时间超过指定秒数,就报出异常,停止访问。
访问超时的设置方法非常简单,只要在requests.get(url, headers=headers)中再加一个timeout=10(10代表10秒,可以改为自己想设定的秒数),代码如下:
res = requests.get(url, headers=headers, timeout=10).text
这样当访问指定网址10秒还没有响应时,就会停止访问并报出异常。实战中通常会用try/except语句来处理异常,这样报出异常也不会影响整体程序的运行,代码如下:

def baidu(company):
    url = 'https://www.baidu.com/s?tn=news&rtt=1&bsst=1&cl=2&wd=' + company
    res = requests.get(url, headers=headers, timeout=10).text
    # 此处省略的是3.1节中的数据提取、清洗代码,注意缩进,详见代码文件


companys = ['华能信托','阿里巴巴','万科集团','百度集团','腾讯','京东']
for i in companys:

    try:
        baidu(i)
        print(i + '爬取成功')
    except:
        print(i + '爬取失败')

在实战中,其实很少会遇到访问超时的情况,似乎省略timeout参数也没什么问题。但是,对于24小时不间断批量爬取多个网址的情况,一旦中途有某个网址访问超时,程序就会陷入“假死”,不能继续爬取其他网址,导致时间上的浪费。所以实战中最好还是要设定timeout参数。

3.5 搜狗新闻与新浪财经数据挖掘实战

掌握了百度新闻的爬取之后,便可以用同样的方法对其他类似网站进行数据挖掘了,如搜狗新闻、新浪财经、新浪微博、四大证券报、微信公众号文章等。本节以搜狗新闻、新浪财经为例进行详细介绍。

3.5.1 搜狗新闻数据挖掘实战

代码文件:3.5.1 搜狗新闻数据挖掘实战.py
搜狗新闻的网址为https://news.sogou.com/。搜狗新闻和百度新闻的网页结构比较类似,所以爬取思路也类似。如下图所示,以在搜狗新闻中搜索“阿里巴巴”为例,注意搜索类型要选“新闻”,不要选“网页”。网址为:

https://news.sogou.com/news?mode=1&sort=0&fixrank=1&query=%B0%A2%C0%EF%B0%CD%B0%CD&shid=djt1(该网址经过删减处理)。

1.获取网页源代码
首先,用如下代码尝试获取网页源代码:

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://news.sogou.com/news?mode=1&sort=0&fixrank=1&query=%B0%A2%C0%EF%B0%CD%B0%CD&shid=djt1'
res = requests.get(url, headers=headers, timeout=10).text
print(res)

运行之后发现获取的网页源代码完整且不存在乱码,那么数据挖掘的第一步就成功了。
网址中的“%B0%A2%C0%EF%B0%CD%B0%CD”是“阿里巴巴”在浏览器中“翻译”后的“语言”,可以直接换成“阿里巴巴”,如下所示:

url = 'https://news.sogou.com/news?mode=1&sort=0&fixrank=1&query=阿里巴巴&shid=djt1'

2.编写正则表达式
下面来提取新闻网址、新闻标题和发布日期,新闻来源的提取留给大家自己练习。
首先提取新闻网址和标题。在浏览器中按F12键,打开开发者工具,然后查看新闻网址和标题的网页源代码,或者按快捷键Ctrl+F,在网页源代码里查找标题,以寻找正则表达式的匹配规则。
第一条新闻的网址对应的网页源代码为:

<a href="http://www.sohu.com/a/284216921_114986" id="uigs_0" target
="_blank">

第二条新闻的网址对应的网页源代码为:

<a href="http://www.sohu.com/a/323388085_643591" id="uigs_1" target
="_blank">

经过对比可以看到,除了我们要获取的href的值外,id的值也在变化,具体来说,是“uigs_”后面的数字在变化,所以可以总结出新闻网址的网页源代码的表达式如下:

<a href="网址" id="uigs_序号" target="_blank">

在编写正则表达式时,只需把想获取的新闻网址换成(.?),把id里变化的内容换成.?:

p_href = '<a href="(.*?)" id="uigs_.*?" target="_blank">'

带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1

import re
p_href = '<a href="(.*?)" id="uigs_.*?" target="_blank">'
href = re.findall(p_href, res)
print(href)
print(len(href)) # 打印输出提取到多少条新闻,检查是否提取完整

用同样的方法观察新闻标题的网页源代码的规律,得到如下表达式:

<a href="网址" id="uigs_序号" target="_blank">标题</a>

在编写正则表达式时把网址和序号换成.?,把标题换成(.?),代码如下:

p_title = '<a href=".*?" id="uigs_.*?" target="_blank">(.*?)</a>'

发布日期的提取思路和之前百度新闻案例的思路类似,这里因为不提取新闻来源,所以可以直接编写如下所示的代码:

p_date = '<p class="news-from">.*? (.*?)</p>'

所有正则表达式相关的代码汇总如下:

p_title = '<a href=".*?" id="uigs_.*?" target="_blank">(.*?)</a>'
title = re.findall(p_title, res)
p_href = '<a href="(.*?)" id="uigs_.*?" target="_blank">'
href = re.findall(p_href, res)
p_date = '<p class="news-from">.*? (.*?)</p>'
date = re.findall(p_date, res)

可以通过类似print(title)和print(len(title))这样的代码打印输出提取到的列表的内容和长度,检查自己编写的正则表达式提取到的信息是否完整。
3.数据清洗
与百度新闻的数据清洗类似,主要运用sub()函数替换不需要的内容,运用strip()函数清除换行符和空格等内容,最后通过print()函数将清洗结果打印输出。由于这里提取的内容不包含换行符和空格,所以未使用strip()函数,代码如下:

for i in range(len(title)):
    title[i] = re.sub('<.*?>', '', title[i])
    title[i] = re.sub('&.*?;', '', title[i])
    date[i] = re.sub('<.*?>', '', date[i])
    print(str(i+1) + '.' + title[i] + '-' + date[i])
    print(href[i])

第3行代码中的'&.*?;'代表所有形式为'&内容;'的非相关文本,因为新闻标题里有时会出现这种形式的文本,需要将其批量替换掉。
4.定义及调用函数
和3.2.1小节类似,这里首先定义一个名为sogou的函数,并且把爬取的网址中的“阿里巴巴”换成变量company,代码如下:

def sogou(company): 
    url = 'https://news.sogou.com/news?mode=1&sort=0&fixrank=1&query=' + company + '&shid=djt1'

定义完函数后,便可以批量调用函数,实现多家公司信息的爬取了,完整代码如下:

import requests
import re
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'}

def sogou(company):
    url = 'https://news.sogou.com/news?mode=1&sort=0&fixrank=1&query=' + company + '&shid=djt1'
    res = requests.get(url,headers=headers, timeout=10).text

    # 编写正则表达式提取数据
    p_title = '<a href=".*?" id="uigs_.*?" target="_blank">(.*?)</a>'
    title = re.findall(p_title, res)
    p_href = '<a href="(.*?)" id="uigs_.*?" target="_blank">'
    href = re.findall(p_href, res)
    p_date = '<p class="news-from">.*? (.*?)</p>'
    date = re.findall(p_date, res)


    # 数据清洗及打印输出
    for i in range(len(title)):
        title[i] = re.sub('<.*?>', '', title[i])
        title[i] = re.sub('&.*?;', '', title[i])
        date[i] = re.sub('<.*?>', '', date[i])
        print(str(i+1) + '.' + title[i] + '-' + date[i])
        print(href[i])

companys = ['华能信托', '阿里巴巴', '万科集团', '百度', '腾讯', '京东']
for i in companys:
    try:
        sogou(i)
        print(i + '搜狗新闻爬取成功')
    except:
        print(i + '搜狗新闻爬取失败')

3.5.2 新浪财经数据挖掘实战

代码文件:3.5.2 新浪财经数据挖掘实战.py
新浪财经的网址为https://finance.sina.com.cn/。新浪财经是金融新闻类数据挖掘很重要的一个数据来源,它的新闻质量一般都很高。
还是以在新浪财经中搜索“阿里巴巴”为例,此时浏览器地址栏中的网址为:

https://search.sina.com.cn/?q=%B0%A2%C0%EF%B0%CD%B0%CD&range=all&c=news&sort=time

,如下图所示。
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
如果将网址中含有百分号的那一串文本直接换成“阿里巴巴”,打开的会是其他网页,这与搜狗新闻的网址不同。该问题对爬取一家公司的信息不会有影响,但是会对之后定义函数及批量爬取多家公司的信息造成不便。所以,如果需要进行批量爬取,最好先对网址是否支持中文进行测试。如果测试结果是不支持中文,可以尝试用下面介绍的办法解决。
这种不能自动转换中文的网址比较少见,其主要原因涉及字符串的编码。因为新浪财经网址的默认编码为gbk中文编码,而通常网址对gbk编码的中文进行识别时会出现错误。关于编码的详细知识见5.2节,这里主要讲解决办法。比较简单的解决办法,是在原来的网址末尾加上“&ie=utf-8”,这样就算前面输入的是中文,对网址也没有影响。添加的“&ie=utf-8”用于声明该网址是基于utf-8编码的,而utf-8编码的中文内容是可以被网址认可的。如下所示,添加完“&ie=utf-8”后就可以直接使用中文“阿里巴巴”了。

url = 'https://search.sina.com.cn/?q=阿里巴巴&range=all&c=news&sort
=time&ie=utf-8'

1.获取网页源代码
首先,用如下代码尝试获取网页源代码:

url = 'https://search.sina.com.cn/?q=阿里巴巴&range=all&c=news&sort
=time&ie=utf-8'
res = requests.get(url, headers=headers, timeout=10).text
print(res)

运行之后获取的网页源代码中没有出现乱码,并且能找到需要的内容,这一步就算成功了。需要注意的是,有时按上面的网址访问新浪财经时,可能会跳转到新浪微博(可能性较小但会发生),获取到的网页源代码中就不会有想要的内容,此时重新运行代码即可。
2.编写正则表达式
使用前面介绍的方法观察新闻网址和标题对应的网页源代码,可以发现如下规律:

<h2><a href="网址" target="_blank">标题</a>

所以提取新闻网址的正则表达式如下:

p_href = '<h2><a href="(.*?)" target="_blank">'

提取新闻标题的正则表达式如下:

p_title = '<h2><a href=".*?" target="_blank">(.*?)</a>'

观察新闻日期对应的网页源代码,可以发现如下规律:

<span class="fgray_time">来源和日期</span>

所以提取新闻日期的正则表达式如下:

p_date = '<span class="fgray_time">(.*?)</span>'

这样提取到的内容会包含来源和日期,在后续进行数据清洗时把它们分割开即可。
整体代码如下所示,此时可以通过print()函数将提取到的内容打印输出,看看效果。

p_title = '<h2><a href=".*?" target="_blank">(.*?)</a>'
p_href = '<h2><a href="(.*?)" target="_blank">'
p_date = '<span class="fgray_time">(.*?)</span>'
title = re.findall(p_title, res)
href = re.findall(p_href, res)
date = re.findall(p_date, res)

3.数据清洗
采用与前面类似的数据清洗方法,通过如下代码处理并打印输出:

for i in range(len(title)):
    title[i] = re.sub('<.*?>', '', title[i])
    date[i] = date[i].split(' ')[1]  # 提取来源和日期中的第二个元素:日期
    print(str(i + 1) + '.' + title[i] + ' - ' + date[i])
    print(href[i])

4.定义及调用函数
和3.2.1小节类似,这里首先定义一个名为xinlang的函数,并且把爬取的网址中的“阿里巴巴”换成变量company,代码如下:

def xinlang(company):
    url = 'https://search.sina.com.cn/?q=' + company + '&range=all
&c=news&sort=time&ie=utf-8'

定义完函数后,便可以批量调用函数,实现多家公司信息的爬取了,完整代码如下:

import requests
import re
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'}

def xinlang(company):
    url = 'https://search.sina.com.cn/?q=' + company + '&range=all

&c=news&sort=time&ie=utf-8'
    res = requests.get(url, headers=headers, timeout=10).text
    # print(res)

    p_title = '<h2><a href=".*?" target="_blank">(.*?)</a>'
    p_href = '<h2><a href="(.*?)" target="_blank">'
    p_date = '<span class="fgray_time">(.*?)</span>'
    title = re.findall(p_title, res)
    href = re.findall(p_href, res)
    date = re.findall(p_date, res)

    for i in range(len(title)):
        title[i] = re.sub('<.*?>', '', title[i])
        date[i] = date[i].split(' ')[1]
        print(str(i + 1) + '.' + title[i] + ' - ' + date[i])
        print(href[i])

companys = ['华能信托', '阿里巴巴', '万科集团', '百度', '腾讯', '京东']

for i in companys:
    try:
        xinlang(i)
        print(i + '新浪新闻爬取成功')
    except:
        print(i + '新浪新闻爬取失败')

至此,我们已经可以从百度新闻、搜狗新闻、新浪财经爬取新闻舆情数据,对于大部分上市公司来说,足以实现新闻舆情监控与预警。下面简单总结一下网络数据挖掘的思路:
第一步:获取网页源代码;
第二步:通过正则表达式提取想要的内容;
第三步:数据清洗;
第四步:定义函数并通过循环调用函数进行批量爬取。
学习完本章,我们应该能够对大部分网站利用爬虫技术进行数据挖掘,并且能够实现24小时多家公司信息的不间断更新。当然,有些网站的数据挖掘有一定难度,例如,2.1.2小节提到过,如果网站内容是动态渲染出来的,如新浪财经的股票数据,就没有办法通过常规的Requests库进行爬取,相应的处理方法将在第8章进行讲解。

上一篇:linux三剑客之awk


下一篇:MySQL超时参数以及相关数据集成、DataX数据同步案例分享