python

背景

爬虫小练习:从新浪财经网站上爬取上市公司海螺水泥的利润表数据,以 JSON 文件与 MySQL 作为两种持久化方式,并实现对公司近10年的营业总收入和营业总成本的数据可视化。eg: https://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600585/ctrl/2013/displaytype/4.phtml

爬虫、保存为JSON、作图

为简化问题的难度,分步来处理:

数据抓取:爬取原始数据,保存为JSON;

数据预处理:将每一个元素的第一个值作为属性名,剩余元素作为值(便于下一步的JSON合并);

数据合并:将10年的数据合并为一个JSON。

数据可视化:使用

数据抓取

先爬取1年的数据。

import requests

from bs4 import BeautifulSoup

import json

# 发送HTTP请求获取网页内容

url = "https://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600585/ctrl/2013/displaytype/4.phtml"

response = requests.get(url)

html_content = response.text

# 使用BeautifulSoup解析HTML内容

soup = BeautifulSoup(html_content, "html.parser")

# 找到表格元素

table = soup.find("table", {"id": "ProfitStatementNewTable0"})

# 提取表格数据

data = []

for row in table.find_all("tr"):

    row_data = []

    for cell in row.find_all("td"):

        row_data.append(cell.text.strip())

    # 过滤掉空行

    if len(row_data) != 0:

        data.append(row_data)

    print(data)

# 将JSON数据保存到文件

with open("data.json", "w", encoding="utf-8") as f:

    json.dump(data, f, ensure_ascii=False)


10年的数据

import requests

from bs4 import BeautifulSoup

import json

# 近10年的利润表

years = 10

for i in range(years):

    # 发送HTTP请求获取网页内容

    url = "https://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600585/ctrl/{}/displaytype/4.phtml".format(2013 + i)

    response = requests.get(url)

    html_content = response.text

    # 使用BeautifulSoup解析HTML内容

    soup = BeautifulSoup(html_content, "html.parser")

    # 找到表格元素

    table = soup.find("table", {"id": "ProfitStatementNewTable0"})

    # 提取表格数据

    data = []

    for row in table.find_all("tr"):

        row_data = []

        for cell in row.find_all("td"):

            row_data.append(cell.text.strip())

        # 过滤掉空行与不完整的数据行

        if len(row_data) > 1:

            data.append(row_data)

        print(data)

    # 将JSON数据保存到文件

    with open("data-{}.json".format(2013 + i), "w", encoding="utf-8") as f:

        json.dump(data, f, ensure_ascii=False)


数据预处理

将每一年的数据,转换为 JSON 格式的 key:value 形式,方便后续的合并操作。

import json

# 循环读取JSON文件

years = 10

for i in range(years):

    with open('data-{}.json'.format(2013+i), 'r', encoding="utf-8") as f:

        first_data = json.load(f)

    result = {}

    for item in first_data:

        # 转换:将每一个元素的第一个值作为属性名,剩余元素作为值

        result.update([(item[0], item[1:])])

        # 将数据保存为JSON格式

        json_data = json.dumps(result)

        # 将JSON数据保存到文件

        with open("data-transform-{}.json".format(2013 + i), "w", encoding="utf-8") as f:

            json.dump(result, f, ensure_ascii=False)


数据合并

将预处理后10年的数据合并为一个大的 JSON 文件。

import json

# 循环读取合并JSON文件

years = 10

merged_data = {}

transformed = {}

for i in range(years):

    with open('data-transform-{}.json'.format(2013+i), 'r', encoding="utf-8") as f:

        first_data = json.load(f)

        transformed[2013 + i] = first_data

        merged_data = {**merged_data, **transformed}


# 将合并后的JSON文件写入磁盘

with open('data-merged.json', 'w', encoding="utf-8") as f:

    json.dump(merged_data, f, indent=4, ensure_ascii=False)

数据可视化

获取 JSON 数据中海螺水泥公司近10年的营业收入与营业成本数据,使用 matplotlib 绘制折线图与柱状图。如果需要做大屏可视化,可以使用AJ-Report开源数据可视化引擎入门实践

import json

import matplotlib.pyplot as plt

with open('data-merged.json', 'r', encoding='utf-8') as f:

    data = json.load(f)


x_data = []

y1_data = []

y2_data = []

for key,value in data.items():

    # print(key)

    print(float(data[key]['营业收入'][0].replace(',', '')))

    print()

    print(float(data[key]['营业成本'][0].replace(',', '')))

    x_data.append(key)

    y1_data.append(float(data[key]['营业收入'][0].replace(',', '')))

    y2_data.append(float(data[key]['营业成本'][0].replace(',', '')))

plt.rcParams['font.sans-serif']=['SimHei']

plt.rcParams['axes.unicode_minus']=False

plt.bar(x_data,y1_data, label='营业收入')

plt.plot(x_data,y2_data, label='营业成本', color='cyan', linestyle='--')

plt.title('海螺水泥近10年营业收入与营业成本')

plt.xlabel('年份')

plt.ylabel('营业收入与营业成本/万元')

# 关闭纵轴的科学计数法

axis_y = plt.gca()

axis_y.ticklabel_format(axis='y', style='plain')

# 图例

plt.legend()

# 显示图表

plt.show()


爬虫、写库

数据表设计(仅部分字段)

根据我们看到网页中的实际表格进行数据表设计。

CREATE TABLE `b_profit_test` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `company_id` BIGINT(20) NOT NULL DEFAULT '0' COMMENT '公司ID', `total_operating_income` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '一、营业总收入', `operating_income` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '营业收入', `total_operating_cost` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '二、营业总成本', `operating_cost` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '营业成本', `taxes_and_surcharges` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '营业税金及附加', `sales_expense` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '销售费用', `management_costs` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '管理费用', `financial_expenses` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '财务费用', `rd_expenses` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '研发费用', `operating_profit` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '三、营业利润', `net_profit` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '五、净利润', `basic_earnings_per_share` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '基本每股收益', `diluted_earnings_per_share` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '稀释每股收益', `report_period` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '报告期' COLLATE 'utf8_general_ci', `date` DATE NULL DEFAULT NULL COMMENT '日期', PRIMARY KEY (`id`) USING BTREE ) COMMENT='利润表测试' COLLATE='utf8_general_ci' ENGINE=InnoDB ;

数据爬取与数据存储

与上面爬虫的方式不同,这里使用 pandas 的 read_html 方法直接获取网页中的表格数据,这里注意目标表格的编号,需要到浏览器做检查与定位。

以单个链接为例,爬虫的结果为一个二维表格,通过转置、将第一行设置为 header 、替换列名(方便与数据表字段对应)、删除整行都为 NaN 的行、单个 NaN 替换为0等一系列的数据预处理操作后,直接连接 MySQL 数据库完成数据落库。

import pandas as pd import pymysql # 爬虫获取网页中的表格,注意这里的参数13,与实际的网页中表格有关 df=pd.read_html('https://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600585/ctrl/2013/displaytype/4.phtml')[13] # 可在Jupyter Notebook中直接查看整个表格 df.head(32) # 预处理 df = df.transpose() # 转置,方便处理 df = df.rename(columns=df.iloc[0]).drop(df.index[0]) # 将第一行设置为header df.rename(columns={'报表日期': 'report_period', '一、营业总收入': 'total_operating_income','营业收入': 'operating_income','二、营业总成本': 'total_operating_cost','营业成本': 'operating_cost','营业税金及附加': 'taxes_and_surcharges','销售费用': 'sales_expense','管理费用': 'management_costs','财务费用': 'financial_expenses','三、营业利润': 'operating_profit','五、净利润': 'net_profit','基本每股收益(元/股)': 'basic_earnings_per_share','稀释每股收益(元/股)': 'diluted_earnings_per_share'},inplace=True) df.dropna(axis=0, how='all', inplace=True) # 删除整行都为NaN的行 df.fillna(0, inplace=True) # 单个NaN替换为0 # 写库 conn = pymysql.connect( user = 'root', host = 'localhost', password= 'root', db = 'financial-statement', port = 3306, ) cur = conn.cursor() for index, row in df.iterrows(): # print(index) # 输出每行的索引值 # print(row) # 输出每行的索引值 sql = "insert into b_profit_test(report_period, total_operating_income, operating_income, total_operating_cost, operating_cost, taxes_and_surcharges, sales_expense, management_costs, financial_expenses, operating_profit, net_profit, basic_earnings_per_share, diluted_earnings_per_share) values ('" + str(row['report_period']) + "'," + str(row['total_operating_income']) + ',' + str(row['operating_income']) + ',' + str(row['total_operating_cost']) + ',' + str(row['operating_cost']) + ',' + str(row['taxes_and_surcharges']) + ',' + str(row['sales_expense']) + ',' + str(row['management_costs']) + ',' + str(row['financial_expenses']) + ',' + str(row['operating_profit']) + ',' + str(row['net_profit']) + ',' + str(row['basic_earnings_per_share']) + ',' + str(row['diluted_earnings_per_share']) + ');' print(sql) cur.execute(sql) conn.commit() cur.close() conn.close()
完成了数据的落库,后续就任由我们发挥了:数据建模、数据分析、数据可视化。。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容