前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >精品教学案例 | 基于Python3的证券之星数据爬取

精品教学案例 | 基于Python3的证券之星数据爬取

原创
作者头像
数据酷客
修改2020-05-12 14:17:48
2.7K0
修改2020-05-12 14:17:48
举报

本案例适合作为大数据专业数据采集课程的配套教学案例。通过本案例,能够达到以下教学效果:

  • 培养学生采集网页中数据的能力。案例详细介绍了如何对证券之星网站上的大量股票信息进行数据采集。
  • 帮助学生熟悉爬取网页数据的主要流程。例如:访问网站、网页解析、获取数据以及数据的展现和存储。
  • 提高学生动手实践能力。案例中使用Python中的urllib库、requests库访问网站,使用bs4库、lxml库解析网页,并比较了它们的区别,最后用sqlite3库将其导入数据库存储到本地。

1.证券之星

证券之星始创于1996年,纳斯达克上市公司——中国金融在线旗下网站,是中国最早的理财服务专业网站,是专业的投资理财服务平台,是中国最大的财经资讯网站与移动财经服务提供商,同时也是中国最领先的互联网媒体。——百度百科

找到我们需要的网站:http://quote.stockstar.com/stock/ranklist_a.shtml

2.网页分析

首先,先对需要获取的地方右键单击,再左键单击检查元素

可以看到“代码”2个字,是处于<a>标签中,再往外是一个<td>标签。而一整行表头都处于<thead class="tbody_right">中。

股票信息都处于<tbody class="tbody_right" id="datalist">标签中,将其展开看一下每一行和每一项的标签。

将标签展开,根据观察可以得出,一整行的数据都在<tr>标签中,每一项都在其下的<td>标签中,其中代码和简称还有一个带有超链接的<a>。至此,该页的数据获取分析结束。

接下来是想办法获取下一页内容,然而“证券之星”的“下一页”是通过JavaScript加载的,在html中无法简单地获取其信息。不过这不成问题,先点击下一页比较一下区别。

点了下一页之后网址发生了变化,通过分析网址,可以得出"ranklist_a"之前的内容为固定内容,"ranklist_a"表示沪深A股,"ranklist_a"之后的"3_1_2"才是我们需要改变的参数。通过改变参数,我们可以发现第一个参数"3"表示根据“涨跌幅”排序(因为表头的“简称”排除之后,“涨跌幅”在第3个),第二个参数“1”表示降序排列,第三个参数“2”表示页数。于是,我们发现可以通过调整网址的最后一个参数来进行翻页操作。

那么,对于这个网页的分析就结束了,下面开始代码实现的部分。

3.代码实现

3.1 解析网页

代码语言:javascript
复制
from bs4 import BeautifulSoup
import urllib


# 需要解析的目标地址
url = 'http://quote.stockstar.com/stock/ranklist_a_3_1_1.html'

# 访问目标地址
response = urllib.request.urlopen(url, timeout=60).read()

# 解析网页
soup = BeautifulSoup(response, 'html5lib', from_encoding='gb2312')

代码释义:

1.urllib.request.urlopen()函数实现对目标地址的访问

函数原型为:urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)

其中,需要了解的有:

url:需要打开的网址

data:Post提交的数据

timeout:设置网站的访问超时时间

2.BeautifulSoup()函数实现对网页的解析

传入BeautifulSoup()一般需要3个参数:文档、解析器、编码方式。

将一段文档传入BeautifulSoup的构造方法,BeautifulSoup会将其解析,就能得到一个文档的对象, 可以传入一段字符串或一个文件句柄。

解析器可以自己选用,这里选用的是"html5lib",主要的解析器及其优缺点如下图所示:

推荐使用lxml和html5lib。另外,如果一段HTML或XML文档格式不正确,那么在不同解析器中返回的结果可能不一样,具体可以查看解析器之间的区别

同时,urllib库可以用requests库替换,bs4库可以用lxml库替换,具体使用方法如下:

代码语言:javascript
复制
from lxml import etree
import requests


# 需要解析的目标地址
url = 'http://quote.stockstar.com/stock/ranklist_a_3_1_1.html'

# 访问目标地址
respond = requests.get(url).text

# 解析网页
tree = etree.HTML(respond)

代码释义:

1.requests.get()函数发送一个GET请求

函数原型为:requests.get(url, params=None, **kwargs)

其中,需要了解的有:

url:需要发送Request的对象地址

params:(可选)以字典形式传递参数

2.etree.HTML()从字符串中以树的结构解析HTML文档,返回解析后的根节点。

函数原型为:HTML(text, parser=None, base_url=None)

其中,需要了解的有:

text:需要解析成HTML文档的字符串

parser:传入参数以使用不同的HTML解析器

base_url:网站根地址,用于处理网页资源的相对路径

以上两种方式分别使用了urllib库bs4库requests库lxml库。虽然使用的库不同,但是步骤都是先访问网页并获取网页文本文档(urllib库requests库),再将其传入解析器(bs4库lxml库)。值得一提的是,这两个例子中的搭配可以互换。

代码语言:javascript
复制
url = 'http://quote.stockstar.com/stock/ranklist_a_3_1_1.html'

response = urllib.request.urlopen(url, timeout=60).read()
tree = etree.HTML(response)

respond = requests.get(url).text
soup = BeautifulSoup(respond, 'html5lib')

如上所示:urllib库lxml库requests库bs4库这样的方式也是可行的。

urllib库和requests库的对比

在目前的需求下,urllib库和requests库的差距体现不大,但是实际上二者还是有如下区别:

  1. 构建参数

urllib库在请求参数时需要用urlencode()对参数进行编码预编译,而requests库只需要把参数传入get()中的params

  1. 请求数据

urllib库需要拼接一个url字符串,而requests库只需要将get()中的base_url填写完善即可

  1. 连接方式

urllib库每次请求结束关闭socket通道,而requests库多次重复使用一个socket,消耗更少资源

  1. 编码方式

requests库的编码方式更加完备

bs4库和lxml库的对比

一提到网页解析技术,提到最多的关键字就是BeautifulSoup和xpath,而它们各自在Python中的模块分别就是bs4库和lxml库。以下是它们的区别:

  1. 效率

一般来说,xpath的效率优于BeautifulSoup。BeautifulSoup是基于DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多。进行分步调试时,生成soup对象时会有明显的延迟。lxml.etree.HTML(html)在step over的一瞬间便成功构建了一个可执行xpath操作的对象。并且lxml库只会进行局部遍历。

  1. 难度

个人认为bs4库比lxml库更容易上手。一方面是使用门槛,BeautifulSoup中的各种方法,看了文档就能用;而lxml需要通晓xpath语法,这意味着需要同时学习xpath语法和查询API文档。另一方面是返回结果,lxml中的xpath()方法返回对象始终是一个list,处理起来比较尴尬;而BeautifulSoup中的方法相对灵活,适合不同场合。

  1. 适用场合

这里主要提一下使用禁区。当遇到list嵌套list的时候,尽量不选择BeautifulSoup而使用xpath,因为BeautifulSoup会用到2个find_all(),而xpath会省下不少功夫。当遇到所需获取的类名有公共部分时,可以使用BeautifulSoup而非xpath,因为xpath需要完全匹配,也就是除了公共部分之外各自独有的部分也得匹配,这样就需要多行代码来获取,而BeautifulSoup可以仅匹配公共部分就获取所有匹配上的类。

3.2 获取数据

在本案例中,所有由bs4库获取的内容都可以用同样的逻辑思路用lxml库获取,因此将用bs4库先作演示如何获取内容,再直接根据bs4库提到的标签,直接写出lxml库的代码。

通过查看网页源码可以看到,该网页并没有使用常见的"utf-8"编码,而使用了"gb2312"编码。

至此,我们确定了传入BeautifulSoup()的3个参数,此时print(soup)会看到的是目标网址的源代码。如果有心去浏览一下,会发现在print出来的源代码中,是找不到“下一页”这3个字的。

接下来通过soup获取数据

代码语言:javascript
复制
soup.find('thead', class_='tbody_right').find_all('td')

可以看到这样就以类似于列表的方式获取了数据表格的表头,只是表头还被标签框着,因此还需要进一步进行处理。

代码语言:javascript
复制
[i.get_text() for i in soup.find('thead', class_='tbody_right').find_all('td')]

代码释义:

.get_text()是完成从标签里取出正文内容这一步。

[expression for iter_val in iterable]是Python中一种根据已有列表,高效创建新列表的方式,是迭代机制的一种应用。

代码语言:javascript
复制
' '.join([i.get_text() for i in soup.find('thead', class_='tbody_right').find_all('td')])

代码释义:

str.join(sequence)方法用于将序列中的元素以指定的字符连接生成一个新的字符串。sequence为要连接的元素序列。str为需要作为连接符的字符。

这里使用它是为了方便展示。

同理可得获取股票数据的代码

代码语言:javascript
复制
# 显示前5行
[' '.join([j.get_text() for j in i.find_all('td')]) for i in soup.find('tbody', class_='tbody_right').find_all('tr')][0:5]

BeautifulSoup获取表头的代码为:

[i.get_text() for i in soup.find('thead', class_='tbody_right').find_all('td')]

先找到了class值为tbody_rightthead标签,再在该范围下寻找了所有的td标签,最后提取正文。基于该逻辑写出XPath语法如下所示:

tree.xpath('//thead[@class="tbody_right"]//td//text()')

为了方便展示,用空格连接每一项:

代码语言:javascript
复制
' '.join(tree.xpath('//thead[@class="tbody_right"]//td//text()'))

BeautifulSoup获取内容的代码为:

[[j.get_text() for j in i.find_all('td')] for i in soup.find('tbody', class_='tbody_right').find_all('tr')]

首先找到了class值为tbody_righttbody标签,并且在该范围下寻找所有的tr标签(对应每一行数据),对于每一个tr标签,再寻找其下所有的td标签,最后提取正文。基于该逻辑写出XPath语法如下所示:

[i.xpath('td//text()') for i in tree.xpath('//tbody[@class="tbody_right"]//tr')]

为了方便展示,用空格连接行中的每一项数据:

代码语言:javascript
复制
# 显示前5行
[' '.join(i.xpath('td//text()')) for i in tree.xpath('//tbody[@class="tbody_right"]//tr')][0:5]

3.3 数据展现和存储

目前为止,已经完成对网址的访问、对网页的解析,得到了我们想要的数据。而数据此时只是单纯的列表或字符形式存在,我们可以用NumPy库、Pandas库将其格式化为DataFrame。DataFrame 类型类似于数据库表结构的数据结构,其含有行索引和列索引,可以将DataFrame 想成是由相同索引的Series组成的Dict类型。在其底层是通过二维以及一维的数据块实现。最后,可以用Python3自带的sqlite3库,将数据本地存储在数据库中。

代码语言:javascript
复制
import numpy as np
import pandas as pd

header = [i.get_text() for i in soup.find('thead', class_='tbody_right').find_all('td')]   # 数据表格的表头
content = [i.xpath('td//text()') for i in tree.xpath('//tbody[@class="tbody_right"]//tr')] # 数据表格的内容

数据中,有一些--出现,这些表示该处数据为空,NumPy中的numpy.NaN在功能上是用来标示空缺数据,因此我们将其先一步进行转化。

代码语言:javascript
复制
content = [[np.nan if j == '--' else j for j in i] for i in content]

pandas.DataFrame()创建DataFrame,并且展示DataFrame及其每列的数据类型。

代码语言:javascript
复制
df = pd.DataFrame(columns=header, data=content)
代码语言:javascript
复制
df.head() # 显示DataFrame的前若干行,默认为5
代码语言:javascript
复制
df.dtypes

DataFrame创建得非常成功,但是美中不足的是,每一列数据都是object类型,并没有识别为数字,接下来将转换它们的数据类型。需要注意的是,“代码”列的数据很容易被识别为数字——这并不是我们想要的,因为如果将其识别为数字,那些0开头的代码将会少于6位数字。

代码语言:javascript
复制
df[df.columns[1:]] = df[df.columns[1:]].apply(pd.to_numeric, errors='ignore')
代码语言:javascript
复制
df.head() # 显示DataFrame的前若干行,默认为5
代码语言:javascript
复制
df.dtypes

对于DataFrame的处理基本完成,接下来将其存入本地数据库。

代码语言:javascript
复制
import sqlite3

db = sqlite3.connect('shares.db')
cursor = db.cursor()
df.to_sql(name='test1', con=db, if_exists='replace')

代码释义:

1.sqlite3.connect(database [,timeout ,other optional arguments])打开一个到 SQLite 数据库文件 database 的链接,如果数据库成功打开,则返回一个连接对象。

database表示连接到的数据库名,timeout参数表示连接等待锁定的持续时间,直到发生异常断开连接。timeout 参数默认是 5.0(5 秒)。如果给定的数据库名称不存在,则该调用将创建一个数据库。如果不想在当前目录中创建数据库,那么可以指定带有路径的文件名,这样就能在任意地方创建数据库。

2.connection.cursor([cursorClass])创建一个cursor,所有的sql语句都将由其执行。

该方法接受一个单一的可选的参数 cursorClass。如果提供了该参数,则它必须是一个扩展自sqlite3.Cursor的自定义的cursor 类。

3.pandas.DataFrame.to_sql(name, con, schema=None, if_exists='fail', index=True, index_label=None, chunksize=None, dtype=None, method=None)将在DataFrame的数据记录写入数据库。

其中,需要了解的参数:

name:SQL的表的名字

con:一般为sqlalchemy.engine.Engine或者sqlite3.Connection

if_exists:如果表已存在,该如何处置,默认返回失败(fail),可以改成在插入新值之前删除表(replace)或者将新值插入现有表(append)

index:默认为Ture,将DataFrame索引写为一列。使用index_label作为表中的列名。

index_label:字符串或序列,默认为None,索引列的列标签。如果给出None(默认值)且 index为True,则使用索引名称。如果DataFrame使用MultiIndex,则应该给出一个序列。

cursor.execute(sql [, optional parameters])从刚存入数据的数据库中提取数据,并且打印数据

代码语言:javascript
复制
cursor.execute('SELECT * FROM test1')
# 同样打印前5行
for row in cursor.fetchall()[0:5]:
    print(row)

3.3 代码整合

虽然urllib库和requests库以及bs4库和lxml库可以二选一,但是此处将其全部用上作为案例中提到的所有代码的整合。此外,将打印前两页数据进行局部展示。

代码语言:javascript
复制
import time
import urllib, requests
from bs4 import BeautifulSoup
from lxml import etree
import numpy as np
import pandas as pd
import sqlite3

# 获取日期作为表名
today = time.strftime('%Y%m%d')
print('获取的数据将存入表:',today)

# 连接数据库,并且创建cursor
db = sqlite3.connect('shares.db')
cursor = db.cursor()

# 如果以今天日期为名的表已经存在。将其删除
cursor.execute('DROP TABLE IF EXISTS "%s";'%today)
db.commit()

# 设置翻页,获取第1~30页(共121页)内容并存入数据库
for n in range(1,31):
    
    # 需要解析的目标地址
    url = 'http://quote.stockstar.com/stock/ranklist_a_3_1_%s.html'%n
    
    # 输出当前获取网站地址
    print('正在获取网站:%s'%url)
    
    # 访问目标地址
    response = urllib.request.urlopen(url, timeout=60).read()
    respond = requests.get(url).text
    
    # 解析网页
    soup = BeautifulSoup(response, 'html5lib', from_encoding='gb2312')
    tree = etree.HTML(respond)
    
    # 获取表头
    header = [i.get_text() for i in soup.find('thead', class_='tbody_right').find_all('td')]
    
    # 获取每条数据
    content = [i.xpath('td//text()') for i in tree.xpath('//tbody[@class="tbody_right"]//tr')]
    
    # 处理数据
    content = [[np.nan if j == '--' else j for j in i] for i in content]
    
    # 创建DataFrame
    df = pd.DataFrame(columns=header, data=content)
    
    # 修改数据类型
    df[df.columns[1:]] = df[df.columns[1:]].apply(pd.to_numeric, errors='ignore')
    
    # 存入数据库,如果today表已存在,新的数据插入旧的数据的后面
    df.to_sql(name=today, con=db, if_exists='append')
    
    # 设置访问时间间隔
    time.sleep(0.5)

print('获取完毕')
db.commit()
db.close()

从数据库中读取数据并创建为DataFrame,再打印数据作为展示

代码语言:javascript
复制
db = sqlite3.connect('shares.db') # 连接数据库
df = pd.read_sql('SELECT "%s" FROM "%s"'%('","'.join(header),today),con=db) # 读取数据创建为DataFrame
代码语言:javascript
复制
 # 显示DataFrame的前若干行,默认为5
df.head()
代码语言:javascript
复制
df.tail() # 显示DataFrame的后若干行,默认为5

4.归纳总结

本案例使用urllib库、requests库访问网站,使用bs4库、lxml库解析网页,并且比较了它们的区别。获取数据后,用NumPy库、Pandas库创建并微调DataFrame,最后用sqlite3库将其导入数据库存在本地。

其中,访问网站、解析网页的库在本案例中可以在一定程度上互换搭配。但是在特殊情况下,它们的特点得到体现,作为使用者应该考虑其特点,选择最合适的库完成代码。在今后的案例中,会适当地提到。

为了让数据不再停留在字符串、列表的形式,将其建立为DataFrame,并且微调了内容和数据类型使其更有条理。最后存入本地数据库让整个数据获取程序更为完整。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.证券之星
  • 2.网页分析
  • 3.代码实现
    • 3.1 解析网页
      • urllib库和requests库的对比
      • bs4库和lxml库的对比
    • 3.2 获取数据
      • 3.3 数据展现和存储
        • 3.3 代码整合
        • 4.归纳总结
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档