利用pandas庫中的read_html方法快速抓取網頁中常見的表格型數據


本文轉載自:https://www.makcyun.top/web_scraping_withpython2.html

 

需要學習的地方:

(1)read_html的用法

作用:快速獲取在html中頁面中table格式的數據

(2)to_sql的用法

將獲得的DataFrame數據寫入數據表中

(3)使用urlencode構造所需的url參數

 

摘要: 我們平常在瀏覽網頁中會遇到一些表格型的數據信息,除了表格本身體現的內容以外,你可能想透過表格再更進一步地進行匯總、篩選、處理分析等操作從而得到更多有價值的信息,這時可用python爬蟲來實現。本文采用pandas庫中的read_html方法來快速准確地抓取表格數據。

本文知識點:

  • Table型表格抓取
  • DataFrame.read_html函數使用
  • 爬蟲數據存儲到mysql數據庫
  • Navicat數據庫的使用

1. table型表格

我們在網頁上會經常看到這樣一些表格,比如:
QS2018世界大學排名

財富世界500強企業排名

IMDB世界電影票房排行榜

中國上市公司信息

他們除了都是表格以外,還一個共同點就是當你點擊右鍵-定位時,可以看到他們都是table類型的表格形式。



從中可以看到table類型的表格網頁結構大致如下:

<table class="..." id="...">
<thead>
<tr>
<th>...</th>
</tr>
</thead>
<tbody>
<tr>
<td>...</td>
</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
...
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</tbody>
</table>

先來簡單解釋一下上文出現的幾種標簽含義:

<table> : 定義表格
<thead> : 定義表格的頁眉
<tbody> : 定義表格的主體
<tr> : 定義表格的行
<th> : 定義表格的表頭
<td> : 定義表格單元

這樣的表格數據,就可以利用pandas模塊里的read_html函數方便快捷地抓取下來。下面我們就來操作一下。

2. 快速抓取

下面以中國上市公司信息這個網頁中的表格為例,感受一下read_html函數的強大之處。

import pandas as pd
import csv

for i in range(1,178): # 爬取全部177頁數據
url = 'http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=%s' % (str(i))
tb = pd.read_html(url)[3] #經觀察發現所需表格是網頁中第4個表格,故為[3]
tb.to_csv(r'1.csv', mode='a', encoding='utf_8_sig', header=1, index=0)
print('第'+str(i)+'頁抓取完成')


只需不到十行代碼,1分鍾左右就可以將全部178頁共3536家A股上市公司的信息干凈整齊地抓取下來。比采用正則表達式、xpath這類常規方法要省心省力地多。如果采取人工一頁頁地復制粘貼到excel中,就得操作到猴年馬月去了。
上述代碼除了能爬上市公司表格以外,其他幾個網頁的表格都可以爬,只需做簡單的修改即可。因此,可作為一個簡單通用的代碼模板。但是,為了讓代碼更健壯更通用一些,接下來,以爬取177頁的A股上市公司信息為目標,講解一下詳細的代碼實現步驟。

3. 詳細代碼實現

3.1. read_html函數

先來了解一下read_html函數的api:

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:可以是url、html文本、本地文件等;
flavor:解析器;
header:標題行;
skiprows:跳過的行;
attrs:屬性,比如 attrs = {'id': 'table'};
parse_dates:解析日期

注意:返回的結果是**DataFrame**組成的**list**。

參考:

1 http://pandas.pydata.org/pandas-docs/stable/io.html#io-read-html
2 http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_html.html

3.2. 分析網頁url

首先,觀察一下中商情報網第1頁和第2頁的網址:

http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=1#QueryCondition
http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=2#QueryCondition

可以發現,只有pageNum的值隨着翻頁而變化,所以基本可以斷定pageNum=1代表第1頁,pageNum=10代表第10頁,以此類推。這樣比較容易用for循環構造爬取的網址。
試着把#QueryCondition刪除,看網頁是否同樣能夠打開,經嘗試發現網頁依然能正常打開,因此在構造url時,可以使用這樣的格式:
http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=i
再注意一下其他參數:
a:表示A股,把a替換為h,表示港股;把a替換為xsb,則表示新三板。那么,在網址分頁for循環外部再加一個for循環,就可以爬取這三個股市的股票了。

3.3. 定義函數

將整個爬取分為網頁提取、內容解析、數據存儲等步驟,依次建立相應的函數。

# 網頁提取函數
def get_one_page(i):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
paras = {
'reportTime': '2017-12-31',
#可以改報告日期,比如2018-6-30獲得的就是該季度的信息
'pageNum': i #頁碼
}
url = 'http://s.askci.com/stock/a/?' + urlencode(paras)
response = requests.get(url,headers = headers)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('爬取失敗')

# beatutiful soup解析然后提取表格
def parse_one_page(html):
soup = BeautifulSoup(html,'lxml')
content = soup.select('#myTable04')[0] #[0]將返回的list改為bs4類型
tbl = pd.read_html(content.prettify(),header = 0)[0]
# prettify()優化代碼,[0]從pd.read_html返回的list中提取出DataFrame

tbl.rename(columns = {'序號':'serial_number', '股票代碼':'stock_code', '股票簡稱':'stock_abbre', '公司名稱':'company_name', '省份':'province', '城市':'city', '主營業務收入(201712)':'main_bussiness_income', '凈利潤(201712)':'net_profit', '員工人數':'employees', '上市日期':'listing_date', '招股書':'zhaogushu', '公司財報':'financial_report', '行業分類':'industry_classification', '產品類型':'industry_type', '主營業務':'main_business'},inplace = True)

print(tbl)
# return tbl
# rename將表格15列的中文名改為英文名,便於存儲到mysql及后期進行數據分析
# tbl = pd.DataFrame(tbl,dtype = 'object') #dtype可統一修改列格式為文本

# 主函數
def main(page):
for i in range(1,page): # page表示提取頁數
html = get_one_page(i)
parse_one_page(html)

# 單進程
if __name__ == '__main__':
main(178) #共提取n頁

上面兩個函數相比於快速抓取的方法代碼要多一些,如果需要抓的表格很少或只需要抓一次,那么推薦快速抓取法。如果頁數比較多,這種方法就更保險一些。解析函數用了BeautifulSoup和css選擇器,這種方法定位提取表格所在的id為#myTable04的table代碼段,更為准確。

3.4. 存儲到MySQL

接下來,我們可以將結果保存到本地csv文件,也可以保存到MySQL數據庫中。這里為了練習一下MySQL,因此選擇保存到MySQL中。

首先,需要先在數據庫建立存放數據的表格,這里命名為listed_company。代碼如下:

import pymysql

def generate_mysql():
conn = pymysql.connect(
host='localhost', # 本地服務器
user='root',
password='******', # 你的數據庫密碼
port=3306, # 默認端口
charset = 'utf8',
db = 'wade')
cursor = conn.cursor()

sql = 'CREATE TABLE IF NOT EXISTS listed_company2 (serial_number INT(30) NOT NULL,stock_code INT(30) ,stock_abbre VARCHAR(30) ,company_name VARCHAR(30) ,province VARCHAR(30) ,city VARCHAR(30) ,main_bussiness_income VARCHAR(30) ,net_profit VARCHAR(30) ,employees INT(30) ,listing_date DATETIME(0) ,zhaogushu VARCHAR(30) ,financial_report VARCHAR(30) , industry_classification VARCHAR(255) ,industry_type VARCHAR(255) ,main_business VARCHAR(255) ,PRIMARY KEY (serial_number))'
# listed_company是要在wade數據庫中建立的表,用於存放數據

cursor.execute(sql)
conn.close()

generate_mysql()

上述代碼定義了generate_mysql()函數,用於在MySQL中wade數據庫下生成一個listed_company的表。表格包含15個列字段。根據每列字段的屬性,分別設置為INT整形(長度為30)、VARCHAR字符型(長度為30) 、DATETIME(0) 日期型等。
在Navicat中查看建立好之后的表格:

接下來就可以往這個表中寫入數據,代碼如下:

import pymysql
from sqlalchemy import create_engine

def write_to_sql(tbl, db = 'wade'):
engine = create_engine('mysql+pymysql://root:******@localhost:3306/{0}?charset=utf8'.format(db))
# db = 'wade'表示存儲到wade這個數據庫中,root后面的*是密碼
try:
tbl.to_sql('listed_company',con = engine,if_exists='append',index=False)
# 因為要循環網頁不斷數據庫寫入內容,所以if_exists選擇append,同時該表要有表頭,parse_one_page()方法中df.rename已設置
except Exception as e:
print(e)

以上就完成了單個頁面的表格爬取和存儲工作,接下來只要在main()函數進行for循環,就可以完成所有總共178頁表格的爬取和存儲,完整代碼如下:

import requests
import pandas as pd
from bs4 import BeautifulSoup
from lxml import etree
import time
import pymysql
from sqlalchemy import create_engine
from urllib.parse import urlencode # 編碼 URL 字符串

start_time = time.time() #計算程序運行時間

def get_one_page(i):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
paras = {
'reportTime': '2017-12-31',
#可以改報告日期,比如2018-6-30獲得的就是該季度的信息
'pageNum': i #頁碼
}
url = 'http://s.askci.com/stock/a/?' + urlencode(paras)
response = requests.get(url,headers = headers)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('爬取失敗')


def parse_one_page(html):
soup = BeautifulSoup(html,'lxml')
content = soup.select('#myTable04')[0] #[0]將返回的list改為bs4類型
tbl = pd.read_html(content.prettify(),header = 0)[0]
# prettify()優化代碼,[0]從pd.read_html返回的list中提取出DataFrame
tbl.rename(columns = {'序號':'serial_number', '股票代碼':'stock_code', '股票簡稱':'stock_abbre', '公司名稱':'company_name', '省份':'province', '城市':'city', '主營業務收入(201712)':'main_bussiness_income', '凈利潤(201712)':'net_profit', '員工人數':'employees', '上市日期':'listing_date', '招股書':'zhaogushu', '公司財報':'financial_report', '行業分類':'industry_classification', '產品類型':'industry_type', '主營業務':'main_business'},inplace = True)

# print(tbl)
return tbl
# rename將中文名改為英文名,便於存儲到mysql及后期進行數據分析
# tbl = pd.DataFrame(tbl,dtype = 'object') #dtype可統一修改列格式為文本

def generate_mysql():
conn = pymysql.connect(
host='localhost',
user='root',
password='******',
port=3306,
charset = 'utf8',
db = 'wade')
cursor = conn.cursor()

sql = 'CREATE TABLE IF NOT EXISTS listed_company (serial_number INT(20) NOT NULL,stock_code INT(20) ,stock_abbre VARCHAR(20) ,company_name VARCHAR(20) ,province VARCHAR(20) ,city VARCHAR(20) ,main_bussiness_income VARCHAR(20) ,net_profit VARCHAR(20) ,employees INT(20) ,listing_date DATETIME(0) ,zhaogushu VARCHAR(20) ,financial_report VARCHAR(20) , industry_classification VARCHAR(20) ,industry_type VARCHAR(100) ,main_business VARCHAR(200) ,PRIMARY KEY (serial_number))'
# listed_company是要在wade數據庫中建立的表,用於存放數據

cursor.execute(sql)
conn.close()


def write_to_sql(tbl, db = 'wade'):
engine = create_engine('mysql+pymysql://root:******@localhost:3306/{0}?charset=utf8'.format(db))
try:
# df = pd.read_csv(df)
tbl.to_sql('listed_company2',con = engine,if_exists='append',index=False)
# append表示在原有表基礎上增加,但該表要有表頭
except Exception as e:
print(e)


def main(page):
generate_mysql()
for i in range(1,page):
html = get_one_page(i)
tbl = parse_one_page(html)
write_to_sql(tbl)

# # 單進程
if __name__ == '__main__': main(178) endtime = time.time()-start_time print('程序運行了%.2f秒' %endtime) # 多進程# from multiprocessing import Pool# if __name__ == '__main__':# pool = Pool(4)# pool.map(main, [i for i in range(1,178)]) #共有178頁# endtime = time.time()-start_time# print('程序運行了%.2f秒' %(time.time()-start_time))

最終,A股所有3535家企業的信息已經爬取到mysql中,如下圖:

最后,需說明不是所有表格都可以用這種方法爬取,比如這個網站中的表格,表面是看起來是表格,但在html中不是前面的table格式,而是list列表格式。這種表格則不適用read_html爬取。得用其他的方法,比如selenium,以后再進行介紹。

本文完。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM