| 知乎专栏 |
我们都知道 Pandas 是做数据分析的,Pandas 在做数据分析之前需要加载数据,加载数据的方法有很多种,数据来源渠道也很多。例如数据可以从 HTML页面中的表格,Excel,JSON,CSV以及关系型数据库等等。
工作中,我们常常需要提取HTML网页中的表格数据,多少会用到爬虫技术。
例如使用 requests 下载网页,然后使用HTML萃取工具,将HTML页面内部table表格中的数据提出去来。这种方法比较复杂,今天介绍的是 Pandas 读取网页中的表格,难度几乎是傻瓜级别的。
在不使用爬虫的情况下,这种方式是最佳选择。
read_html() 可以萃取 HTML table 标签中的数据。
pandas.read_html(io, match='.+', flavor=None, header=None, index_col=None, skiprows=None, attrs=None, parse_dates=False, tupleize_cols=None, thousands=', ', encoding=None, decimal='.', converters=None, na_values=None, keep_default_na=True, displayed_only=True)
详细参数
io: str, path object 或 file-like objectURL,file-like对象或包含HTML的原始字符串。请注意,lxml仅接受http,ftp和文件url协议。如果您的网址以'https'您可以尝试删除's'。
match: str 或 compiled regular expression, 可选参数将返回包含与该正则表达式或字符串匹配的文本的表集。除非HTML非常简单,否则您可能需要在此处传递非空字符串。默认为“。+”(匹配任何非空字符串)。默认值将返回页面上包含的所有表。此值转换为正则表达式,以便Beautiful Soup和lxml之间具有一致的行为。
flavor: str 或 None要使用的解析引擎。 ‘bs4’和‘html5lib’彼此同义,它们都是为了向后兼容。默认值None尝试使用lxml解析,如果失败,它会重新出现bs4+html5lib。
header: int 或 list-like 或 None, 可选参数该行(或MultiIndex)用于创建列标题。
index_col: int 或 list-like 或 None, 可选参数用于创建索引的列(或列列表)。
skiprows: int 或 list-like 或 slice 或 None, 可选参数解析列整数后要跳过的行数。从0开始。如果给出整数序列或切片,将跳过该序列索引的行。请注意,单个元素序列的意思是“跳过第n行”,而整数的意思是“跳过n行”。
attrs: dict 或 None, 可选参数这是属性的词典,您可以传递该属性以用于标识HTML中的表。在传递给lxml或Beautiful Soup之前,不会检查它们的有效性。但是,这些属性必须是有效的HTML表属性才能正常工作。例如, attrs = {'id': 'table'} 是有效的属性字典,因为‘id’ HTML标记属性是任何HTML标记的有效HTML属性,这个文件。 attrs = {'asdf': 'table'} 不是有效的属性字典,因为‘asdf’即使是有效的XML属性,也不是有效的HTML属性。可以找到有效的HTML 4.01表属性这里。可以找到HTML 5规范的工作草案这里。它包含有关现代Web表属性的最新信息。
parse_dates: bool, 可选参数参考read_csv()更多细节。
thousands: str, 可选参数用来解析成千上万个分隔符。默认为','。
encoding: str 或 None, 可选参数用于解码网页的编码。默认为NoneNone保留先前的编码行为,这取决于基础解析器库(例如,解析器库将尝试使用文档提供的编码)。
decimal: str, 默认为 ‘.’可以识别为小数点的字符(例如,对于欧洲数据,请使用“,”)。
converters: dict, 默认为 None用于在某些列中转换值的函数的字典。键可以是整数或列标签,值是采用一个输入参数,单元格(而非列)内容并返回转换后内容的函数。
na_values: iterable, 默认为 None自定义NA值。
keep_default_na: bool, 默认为 True如果指定了na_values并且keep_default_na为False,则默认的NaN值将被覆盖,否则将附加它们。
displayed_only: bool, 默认为 True是否应解析具有“display:none”的元素。
import pandas as pd
html = """
<table border="1">
<tr>
<th>Month</th>
<th>Savings</th>
</tr>
<tr>
<td>January</td>
<td>$100</td>
</tr>
</table>
"""
data = pd.read_html(html)[0]
print(data)
提取多个表格
import pandas as pd
html = """
<table border="1">
<tr><th>月份</th><th>节约</th></tr>
<tr><td>一月</td><td>100</td></tr>
</table>
<table border="1">
<tr><th>月份</th><th>节约</th></tr>
<tr><td>二月</td><td>200</td></tr>
</table>
"""
dfs = pd.read_html(html)
print("发现HTML表格数量:%s" % len(dfs))
for data in dfs:
print(data)
准备 table.html 文件
<table border="1">
<thead>
<tr>
<th>Month</th>
<th>Savings</th>
</tr>
</thead>
<tfoot>
<tr>
<td>Sum</td>
<td>$180</td>
</tr>
</tfoot>
<tbody>
<tr>
<td>January</td>
<td>$100</td>
</tr>
<tr>
<td>February</td>
<td>$80</td>
</tr>
</tbody>
</table>
import pandas as pd
# 错误方法
html = open('table.html', 'r').read()
data = pd.read_html(html)[0]
print(data)
# 正确方法
data = pd.read_html('table.html')[0]
print(data)
获取页面中所有table的数据
import pandas as pd url = "http://www.stats.gov.cn/tjsj/zxfb/202103/t20210330_1815829.html" data = pd.read_html(url) print(data)
多个 table 会返回一个数组,通过数组下标可以读取指定表格。
import pandas as pd url = "http://www.stats.gov.cn/tjsj/zxfb/202103/t20210330_1815829.html" data = pd.read_html(url)[1] print(data)
通过 id 属性,精确提取指定表格中的数据。
import pandas as pd
html = """
<table id="first">
<tr><th>姓名</th><th>性别</th></tr>
<tr><td>张三</td><td>男</td></tr>
</table>
<table id="second">
<tr><th>姓名</th><th>性别</th></tr>
<tr><td>李四</td><td>男</td></tr>
</table>
"""
data = pd.read_html(html, attrs={'id': 'second'})
print(data[0])
获取 html table 标签 id 属性为 oTable 的表格数据
import pandas as pd
url = "http://fund.eastmoney.com/fund.html"
data = pd.read_html(url,attrs = {'id': 'oTable'})
print(data)
HTML 属性,只有 id 是唯一的,其他属性都允许重复使用,例如 class,所以使用 class 会选中多张 HTML 表格。
data = pd.read_html(text, attrs={'class': 'netkiller'})
print(len(data))
我的做法是使用 xpath 精准匹配,为什么部用数组下标了,因为页面的变化很可能造成数字顺序错乱。
import pandas as pd
from lxml import etree
text = """
<table class="netkiller">
<tr><th>姓名</th><th>性别</th></tr>
<tr><td>张三</td><td>男</td></tr>
</table>
<table class="netkiller">
<tr><th>姓名</th><th>性别</th></tr>
<tr><td>李四</td><td>男</td></tr>
</table>
"""
html = etree.HTML(text)
result = html.xpath('//table[@class="netkiller"][last()]')
text = etree.tostring(result[0], encoding='utf-8').decode('utf-8')
data = pd.read_html(text)
print(data[0])
import dominate
from dominate.tags import *
import time
import os
import pandas as pd
os.chdir(os.path.dirname(__file__))
df = pd.read_excel("2022年6月17 Excle.xlsx", '6月17', usecols="A:R")
print("=" * 20, '打印表头列名', "=" * 20)
header = df.columns[4:].tolist()
print(header)
print("=" * 20, '价格', "=" * 20)
price = df.iloc[0][4:].fillna('').to_dict()
print(price)
print("行:{0}".format(df.index.size))
print("列:{0}".format(df.columns.size))
doc = dominate.document(title='Dominate your HTML')
with doc:
with div(id='content'):
for index, row in df.iterrows():
# print(row.to_dict())
t = row[1:].fillna(0).to_dict()
if t['合计'] == 0:
continue
br()
with table(id=index, border=1, cellspacing="0", cellpadding="0", width='50%'):
caption('亲!本期团购清单如下:{0}'.format(
time.strftime('%Y-%m-%d', time.localtime())))
h = tr(align="center")
b = tr(align="center")
thprice = tr(align="center")
for key, value in t.items():
# print(value)
# print(t)
# if not value.isnull():
if value != 0:
h.add(th(key))
b.add(td(value))
if key in price.keys():
thprice.add(td(price[key]))
elif key == '几期':
thprice.add(td('单价'))
else:
thprice.add(td(''))
thead().add(h)
thead().add(thprice)
tbody().add(b)
# tfoot().add(p)
with open('doc.html', 'w') as file:
file.write(doc.render())
# print(doc)
本节主要介绍和excel的交互。
from pandas import DataFrame
data = {
'name': ['Neo', 'Tom', 'Jerry'],
'age': [11, 12, 13],
'gender': ['Y', 'Y', 'N']
}
df = DataFrame(data)
df.to_excel('netkiller.xlsx')
写入指定工作表
from pandas import DataFrame
data = {
'姓名': ['Neo', 'Tom', 'Jerry'],
'年龄': [11, 12, 13],
'性别': ['Y', 'Y', 'N']
}
df = DataFrame(data)
df.to_excel('neo.xlsx', sheet_name='我的工作表')
设置 sheet_name 参数将数据写入指定的工作表
startrow=2 从第几行开始写入, index=False 关闭序号列, header=False 不写入表头
from pandas import DataFrame
data = {
'姓名': ['Neo', 'Tom', 'Jerry'],
'年龄': [11, 12, 13],
'性别': ['Y', 'Y', 'N']
}
df = DataFrame(data)
df.to_excel('neo.xlsx', sheet_name='我的工作表',
startrow=2, index=False, header=False)
写入多个工作表
import pandas as pd
data = pd.DataFrame(
{
'姓名': ['Neo', 'Tom', 'Jerry'],
'年龄': [11, 12, 13],
'性别': ['Y', 'Y', 'N']
}
)
excel = pd.ExcelWriter("sheet.xlsx")
data.to_excel(excel, sheet_name="Sheet1", index=False)
data.to_excel(excel, sheet_name="Sheet2", index=False)
data.to_excel(excel, sheet_name="Sheet3", index=False)
excel.save()
excel.close()
向Excel中追加工作表
# 追加工作表,首先创建 Excel 文件
with pd.ExcelWriter("sheet1.xlsx") as writer:
data.to_excel(writer, sheet_name="Sheet1", index=False)
data.to_excel(writer, sheet_name="Sheet2", index=False)
data.to_excel(writer, sheet_name="Sheet3", index=False)
append = pd.DataFrame(
{
'姓名': ['Neo', 'Tom', 'Jerry'],
'年龄': [11, 12, 13],
'性别': ['Y', 'Y', 'N']
}
)
# 默认ExcelWriter是覆盖模式,需要使用 mode='a' 参数,才能Excel文件中增加工作表
with pd.ExcelWriter("sheet1.xlsx", mode='a', engine='openpyxl') as writer:
append.to_excel(writer, sheet_name="Sheet4", index=False)
append.to_excel(writer, sheet_name="Sheet5", index=False)
read_excel() 参数详解:
加载函数为read_excel(),其具体参数如下。 read_excel(io, sheetname=0, header=0, skiprows=None, skip_footer=0, index_col=None,names=None, parse_cols=None, parse_dates=False,date_parser=None,na_values=None,thousands=None, convert_float=True, has_index_names=None, converters=None,dtype=None, true_values=None, false_values=None, engine=None, squeeze=False, **kwds) 常用参数解析: io : string, path object ; excel 路径。 sheetname : string, int, mixed list of strings/ints, or None, default 0 返回多表使用sheetname=[0,1],若sheetname=None是返回全表 header : int, list of ints, default 0 指定列表头,默认0,即取第一行,数据没有表头设定 skiprows : list-like,Rows to skip at the beginning,跳过指定行数的数据 skip_footer : int,default 0, 省略从尾部数的行数据,默认是 0 index_col : int, list of ints, default None 指定列为索引列 names : array-like, default None, 指定列的名字。
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
import pandas as pd
# 默认读取第一个工作表
df = pd.read_excel("团购2021.xlsx")
data = df.head() # 默认读取前5行的数据
print("当前默认工作表:\n{0}".format(data)) # 格式化输出
获取Excel文件中的工作表
import pandas as pd
xls = pd.ExcelFile("团购2021.xlsx")
sheet_names = xls.sheet_names
print(sheet_names)
打开默认工作表
import pandas as pd
df = pd.read_excel("团购2021.xlsx", None)
print(df.keys())
for k,v in df.items():
print(k)
打开指定工作表
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import pandas as pd file_path = r'团购.xlsx' df = pd.read_excel(file_path, sheet_name="3月2日", header=1)
打开 Excel 并返回指定列数据
import pandas as pd sheet = pd.read_excel(io="new.xlsx", usecols=['name']) print(sheet)
打开多个工作表
import pandas as pd sheet = pd.read_excel(io="测试数据.xlsx", sheet_name=[0, 1]) print(sheet[0])
例 23.1. Pandas 打开工作表的四种方法
import pandas as pd
# 打开一张工作表
df = pd.read_excel("sheet1.xlsx", sheet_name="Sheet1")
print(df)
# 指定并打开多张工作表
df = pd.read_excel("sheet1.xlsx", sheet_name=["Sheet1", "Sheet2", "Sheet3"])
for sheet, data in df.items():
print("=" * 20, sheet, "=" * 20)
print(data)
# 使用数字索引打开多张工作表
df = pd.read_excel("sheet1.xlsx", sheet_name=[0, 1, 2])
for sheet, data in df.items():
print("=" * 20, sheet, "=" * 20)
print(data)
# 使用 ExcelFile 对象打开多个工作表
sheet = {}
with pd.ExcelFile("sheet1.xlsx") as xls:
sheet["Sheet1"] = pd.read_excel(
xls, "Sheet1", index_col=None, na_values=["NA"])
sheet["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1)
for sheet, data in sheet.items():
print("=" * 20, sheet, "=" * 20)
print(data)
import pandas as pd
sheet = pd.read_excel("测试.xlsx", sheet_name=[1, 0])
st = pd.concat(sheet,ignore_index = True)
import pandas as pd
sheet = pd.read_excel("sheet1.xlsx", sheet_name=[1, 0])
df = pd.concat(sheet, ignore_index=True)
print(df)
df.to_excel('merge.xlsx', index=False)
import pandas as pd
df = pd.read_excel("../netkiller.xlsx", index_col=False, usecols="B,C:E")
print("=" * 20, "读取B,C:E列的数据", "=" * 20)
print(df.head())
# 读取B~E列的数据
df = pd.read_excel("../netkiller.xlsx", index_col=False, usecols="B:E")
print("=" * 20, "读取B~E列的数据", "=" * 20)
print(df)
df = pd.read_excel("netkiller.xlsx", index_col=False, usecols=[1, 2, 3])
print("=" * 20, "读取[1, 2, 3]列的数据", "=" * 20)
print(df)
import pandas as pd
from pandas import DataFrame
xlsx = 'skip.xlsx'
data = {
'姓名': ['张三', '李四', '王五', '牛七', '马八', '赵九'],
'年龄': [11, 12, 13, 14, 15, 17],
'性别': ['Y', 'Y', 'N', 'Y', 'N', 'Y']
}
df = DataFrame(data)
df.to_excel(xlsx, index=True, index_label='序号')
df = pd.read_excel(xlsx, skiprows=[1, 3, 4])
print("跳过数据 [1, 3, 4]:\n{0}".format(df))
df = pd.read_excel(xlsx, skiprows=3)
print("跳过前3条数据:\n{0}".format(df))
df = pd.read_excel(xlsx, skipfooter=2)
print("从尾部剪掉1条数据:\n{0}".format(df))
跳过数据 [1, 3, 4]: 序号 姓名 年龄 性别 0 1 李四 12 Y 1 4 马八 15 N 2 5 赵九 17 Y 跳过 top10 数据: 2 王五 13 N 0 3 牛七 14 Y 1 4 马八 15 N 2 5 赵九 17 Y 从尾部剪掉1条数据: 序号 姓名 年龄 性别 0 0 张三 11 Y 1 1 李四 12 Y 2 2 王五 13 N 3 3 牛七 14 Y
import pandas as pd
from pandas import DataFrame
file_path = r'new.xlsx'
df = pd.read_excel(file_path)
df['gender'][df['gender'] == 'N'] = 'Female'
df['gender'][df['gender'] == 'Y'] = 'Male'
print(df)
DataFrame(df).to_excel(
file_path, sheet_name='Sheet1', index=False, header=True)
import pandas as pd
from pandas import DataFrame
file_path = r'new.xlsx'
df = pd.read_excel(file_path)
# 新增一列
df['ctime'] = None
# 新增一行
df.loc[4] = [3, 'Alice', 20, 'Female', '2021-5-11']
print(df)
DataFrame(df).to_excel(
file_path, sheet_name='Sheet2', index=False, header=True)
import pandas as pd
sheet = pd.read_excel("工资表.xlsx", usecols=['工资'])
high_salary = sheet[sheet['工资'] > 10000]
middle_salary = sheet[(sheet['工资'] >= 8000) & (sheet['工资'] <=10000)]
low_salary = sheet[sheet['工资'] < 8000]
high_salary = sheet[(sheet['工资'] >= 8000) & (sheet['工资'] <=10000)][['姓名','工资']]
import numpy as np
import pandas as pd
pd.set_option('max_columns', 10)
pd.set_option('max_rows', 20)
# 禁用科学计数法
pd.set_option('display.float_format', lambda x: '%.2f' % x)
df.to_excel 导出大数据会出现 Excle 表格损坏或没有数据的情况
df = pd.read_sql_query(text(sqlOrder), engine.connect())
df.to_excel("{yesterday}.xlsx".format(yesterday=yesterday), sheet_name='Sheet1', index=False)
解决方案
df = pd.read_sql_query(text(query), connect)
with pd.ExcelWriter(xlsxfile) as writer:
df.to_excel(writer, sheet_name=platform_name.replace(':',''), index=False)
import pandas as pd
months=[202001,202002,202003,202004,202005,202006,202007,202008,202009,202010,202011,202012]
for month in months:
print(month)
weather = pd.read_html(f'http://www.tianqihoubao.com/lishi/wanzhou/month/{month}.html', encoding='gb18030', header=0)[0]
print(weather)
weather.to_csv(f'/tmp/{month}天气预报数据.csv', mode='a+', index=False, header=False)
header=True
df.to_csv(f'/tmp/{month}天气预报数据.csv', mode='a+', index=False, header=True)
dt.to_csv('C:/Users/think/Desktop/Result.csv',sep='?')
dt.to_csv('C:/Users/think/Desktop/Result.csv',sep=':')
# 格式 float_format: Format string for floating point numbers
# 保留两位小数
dt.to_csv('/tmp/neo.csv',float_format='%.2f')
# cols: Columns to write (default None)
dt.to_csv('C:/Users/think/Desktop/Result.csv',columns=['name'])
# index: whether to write row (index) names (default True)
dt.to_csv('/tmp/neo.csv',index=0)
# na_rep: A string representation of a missing value (default ‘’)
dt.to_csv('/tmp/neo.csv',na_rep='NA')
替换NaN(dropna,fillna,isnull)
import pandas as pd
import numpy as np
a = np.arange(25, dtype=float).reshape((5, 5))
# print(len(a))
for i in range(len(a)):
a[i, :i] = np.nan
a[3, 0] = 25.0
df = pd.DataFrame(data=a, columns=list('ABCDE'))
print('-'*20, '原始数据', '-'*20)
print(df)
print('-'*20, '填充0', '-'*20)
print(df.fillna(value=0))
print('-'*20, '向后填充', '-'*20)
print(df.fillna(method='pad'))
print('-'*20, '向前填充', '-'*20)
print(df.fillna(method='backfill'))
print('-'*20, '用字典填充', '-'*20)
values = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4}
print(df.fillna(value=values))
print('-'*20, '只替换第1个NaN值', '-'*20)
print(df.fillna(method='pad', limit=1))
输出结果
-------------------- 原始数据 --------------------
A B C D E
0 0.0 1.0 2.0 3.0 4.0
1 NaN 6.0 7.0 8.0 9.0
2 NaN NaN 12.0 13.0 14.0
3 25.0 NaN NaN 18.0 19.0
4 NaN NaN NaN NaN 24.0
-------------------- 填充0 --------------------
A B C D E
0 0.0 1.0 2.0 3.0 4.0
1 0.0 6.0 7.0 8.0 9.0
2 0.0 0.0 12.0 13.0 14.0
3 25.0 0.0 0.0 18.0 19.0
4 0.0 0.0 0.0 0.0 24.0
-------------------- 向后填充 --------------------
A B C D E
0 0.0 1.0 2.0 3.0 4.0
1 0.0 6.0 7.0 8.0 9.0
2 0.0 6.0 12.0 13.0 14.0
3 25.0 6.0 12.0 18.0 19.0
4 25.0 6.0 12.0 18.0 24.0
-------------------- 向前填充 --------------------
A B C D E
0 0.0 1.0 2.0 3.0 4.0
1 25.0 6.0 7.0 8.0 9.0
2 25.0 NaN 12.0 13.0 14.0
3 25.0 NaN NaN 18.0 19.0
4 NaN NaN NaN NaN 24.0
-------------------- 用字典填充 --------------------
A B C D E
0 0.0 1.0 2.0 3.0 4.0
1 0.0 6.0 7.0 8.0 9.0
2 0.0 1.0 12.0 13.0 14.0
3 25.0 1.0 2.0 18.0 19.0
4 0.0 1.0 2.0 3.0 24.0
-------------------- 只替换第1个NaN值 --------------------
A B C D E
0 0.0 1.0 2.0 3.0 4.0
1 0.0 6.0 7.0 8.0 9.0
2 NaN 6.0 12.0 13.0 14.0
3 25.0 NaN 12.0 18.0 19.0
4 25.0 NaN NaN 18.0 24.0
import pandas as pd
from pandas import DataFrame
import sqlite3
con = sqlite3.connect(":memory:")
data = DataFrame({
'姓名': ['张三', '李四', '王五'],
'年龄': [11, 12, 13],
'性别': ['Y', 'Y', 'N']
})
data.to_sql("data", con)
table = pd.read_sql_query("SELECT * FROM data", con)
print(table)
安装依赖库
neo@MacBook-Pro-Neo ~ % pip install sqlalchemy
创建链接引擎参考实例
from sqlalchemy import create_engine
engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")
engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")
engine = create_engine("oracle://scott:tiger@127.0.0.1:1521/sidname")
engine = create_engine("mssql+pyodbc://mydsn")
# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")
# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db")
DataFrame 结果集很大,写入时间过程很长,容易产生错误,这时可以使用 chunksize 切割数据,分批写入。
from sqlalchemy import create_engine
import pandas as pd
from pandas import DataFrame
engine = create_engine("sqlite:///:memory:")
data = DataFrame({
'姓名': ['张三', '李四', '王五'],
'年龄': [11, 12, 13],
'性别': ['Y', 'Y', 'N']
})
data.to_sql("data", engine, chunksize=1000)
table = pd.read_sql_query("SELECT * FROM data", engine)
print(table)