zoukankan      html  css  js  c++  java
  • 10行代码爬取全国所有A股/港股/新三板上市公司信息

    摘要: 我们平常在浏览网页中会遇到一些表格型的数据信息,除了表格本身体现的内容以外,可能还想透过表格背后再挖掘些有意思或者有价值的信息。这时,可用python爬虫来实现。本文采用pandas库中的read_html方法来快速准确地抓取网页中的表格数据。

    由于本文中含有一些超链接,微信中无法直接打开,所以建议点击最左下角阅读原文阅读,体验更好,也可以复制链接到浏览器打开:

    https://www.makcyun.top/web_scraping_withpython2.html

    本文知识点:

    • Table型表格抓取

    • DataFrame.read_html函数使用

    • MySQL数据库存储

    • Navicat数据库的使用

    1. table型表格

    我们在网页上会经常看到这样一些表格,比如:
    QS2018世界大学排名:
    uploading-image-522548.png

    财富世界500强企业排名:
    uploading-image-571806.png

    IMDB世界电影票房排行榜:
    uploading-image-38035.png

    中国A股上市公司信息:
    uploading-image-513503.png

    它们除了都是表格以外,还一个共同点就是当点击右键-定位时,可以看到它们都是table类型的表格。
    uploading-image-686147.png

    uploading-image-65402.png

    uploading-image-862581.png

    uploading-image-726051.png

    从中可以看到table类型的表格网页结构大致如下:

     1<table class="..." id="...">
     2    <thead>
     3    <tr>
     4    <th>...</th>
     5    </tr>
     6    </thead>
     7    <tbody>
     8        <tr>
     9            <td>...</td>
    10        </tr>
    11        <tr>...</tr>
    12        <tr>...</tr>
    13        <tr>...</tr>
    14        <tr>...</tr>
    15        ...
    16        <tr>...</tr>
    17        <tr>...</tr>
    18        <tr>...</tr>
    19        <tr>...</tr>        
    20    </tbody>
    21</table>
    

    先来简单解释一下上文出现的几种标签含义:

    1<table>    : 定义表格
    2<thead>    : 定义表格的页眉
    3<tbody>    : 定义表格的主体
    4<tr>    : 定义表格的行
    5<th>    : 定义表格的表头
    6<td>    : 定义表格单元
    

    这样的表格数据,就可以利用pandas模块里的read_html函数方便快捷地抓取下来。下面我们就来操作一下。

    2. 快速抓取

    下面以中国上市公司信息这个网页中的表格为例,感受一下read_html函数的强大之处。

    1import pandas as pd
    2import csv
    3
    4for i in range(1,178):  # 爬取全部177页数据
    5    url = 'http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=%s' % (str(i))
    6    tb = pd.read_html(url)[3] #经观察发现所需表格是网页中第4个表格,故为[3]
    7    tb.to_csv(r'1.csv', mode='a', encoding='utf_8_sig', header=1, index=0)
    8    print('第'+str(i)+'页抓取完成')
    

    uploading-image-806178.png

    只需不到十行代码,1分钟左右就可以将全部178页共3535家A股上市公司的信息干净整齐地抓取下来。比采用正则表达式、xpath这类常规方法要省心省力地多。如果采取人工一页页地复制粘贴到excel中,就得操作到猴年马月去了。
    上述代码除了能爬上市公司表格以外,其他几个网页的表格都可以爬,只需做简单的修改即可。因此,可作为一个简单通用的代码模板。但是,为了让代码更健壮更通用一些,接下来,以爬取177页的A股上市公司信息为目标,讲解一下详细的代码实现步骤。

    3. 详细代码实现

    3.1. read_html函数

    先来了解一下read_html函数的api:

     1pandas.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)
     2
     3常用的参数:
     4io:可以是url、html文本、本地文件等;
     5flavor:解析器;
     6header:标题行;
     7skiprows:跳过的行;
     8attrs:属性,比如 attrs = {'id': 'table'};
     9parse_dates:解析日期
    10
    11注意:返回的结果是**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页的网址:

    1 http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=1#QueryCondition
    2 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. 定义函数

    将整个爬取分为网页提取、内容解析、数据存储等步骤,依次建立相应的函数。

     1# 网页提取函数
     2def get_one_page(i):
     3    try:
     4        headers = {
     5            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
     6        }
     7        paras = {
     8        'reportTime': '2017-12-31',   
     9        #可以改报告日期,比如2018-6-30获得的就是该季度的信息
    10        'pageNum': i   #页码
    11        }
    12        url = 'http://s.askci.com/stock/a/?' + urlencode(paras)
    13        response = requests.get(url,headers = headers)
    14        if response.status_code == 200:
    15            return response.text
    16        return None
    17    except RequestException:
    18        print('爬取失败')
    19
    20# beatutiful soup解析然后提取表格
    21def parse_one_page(html):
    22    soup = BeautifulSoup(html,'lxml')
    23    content = soup.select('#myTable04')[0] #[0]将返回的list改为bs4类型
    24    tbl = pd.read_html(content.prettify(),header = 0)[0]
    25    # prettify()优化代码,[0]从pd.read_html返回的list中提取出DataFrame
    26
    27    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)
    28
    29    print(tbl)
    30    # return tbl
    31    # rename将表格15列的中文名改为英文名,便于存储到mysql及后期进行数据分析
    32    # tbl = pd.DataFrame(tbl,dtype = 'object') #dtype可统一修改列格式为文本
    33
    34# 主函数
    35def main(page):
    36    for i in range(1,page):   # page表示提取页数
    37        html = get_one_page(i)
    38        parse_one_page(html)
    39
    40# 单进程
    41if __name__ == '__main__':    
    42    main(178)   #共提取n页
    

    上面两个函数相比于快速抓取的方法代码要多一些,如果需要抓的表格很少或只需要抓一次,那么推荐快速抓取法。如果页数比较多,这种方法就更保险一些。解析函数用了BeautifulSoup和css选择器,这种方法定位提取表格所在的id为#myTable04的table代码段,更为准确。

    3.4. 存储到MySQL

    接下来,我们可以将结果保存到本地csv文件,也可以保存到MySQL数据库中。这里为了练习一下MySQL,因此选择保存到MySQL中。

    首先,需要先在数据库建立存放数据的表格,这里命名为listed_company。代码如下:

     1 import pymysql
     2
     3 def generate_mysql():
     4    conn = pymysql.connect(
     5        host='localhost',   # 本地服务器
     6        user='root',
     7        password='******',  # 你的数据库密码
     8        port=3306,          # 默认端口
     9        charset = 'utf8',
    10        db = 'wade')
    11    cursor = conn.cursor()
    12
    13    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))'
    14    # listed_company是要在wade数据库中建立的表,用于存放数据
    15
    16    cursor.execute(sql)
    17    conn.close()
    18
    19 generate_mysql()
    

    上述代码定义了generate_mysql()函数,用于在MySQL中wade数据库下生成一个listed_company的表。表格包含15个列字段。根据每列字段的属性,分别设置为INT整形(长度为30)、VARCHAR字符型(长度为30) 、DATETIME(0) 日期型等。
    在Navicat中查看建立好之后的表格:

    uploading-image-471134.png

    uploading-image-54744.png

    接下来就可以往这个表中写入数据,代码如下:

     1 import pymysql
     2 from sqlalchemy import create_engine
     3
     4 def write_to_sql(tbl, db = 'wade'):
     5    engine = create_engine('mysql+pymysql://root:******@localhost:3306/{0}?charset=utf8'.format(db))
     6    # db = 'wade'表示存储到wade这个数据库中,root后面的*是密码
     7    try:
     8        tbl.to_sql('listed_company',con = engine,if_exists='append',index=False)
     9        # 因为要循环网页不断数据库写入内容,所以if_exists选择append,同时该表要有表头,parse_one_page()方法中df.rename已设置
    10    except Exception as e:
    11        print(e)
    

    以上就完成了单个页面的表格爬取和存储工作,接下来只要在main()函数进行for循环,就可以完成所有总共178页表格的爬取和存储,完整代码如下:

    1import requests
     2import pandas as pd
     3from bs4 import BeautifulSoup
     4from lxml import etree
     5import time
     6import pymysql
     7from sqlalchemy import create_engine
     8from urllib.parse import urlencode  # 编码 URL 字符串
     9
    10start_time = time.time()  #计算程序运行时间
    11
    12def get_one_page(i):
    13    try:
    14        headers = {
    15            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
    16        }
    17        paras = {
    18        'reportTime': '2017-12-31',
    19        #可以改报告日期,比如2018-6-30获得的就是该季度的信息
    20        'pageNum': i   #页码
    21        }
    22        url = 'http://s.askci.com/stock/a/?' + urlencode(paras)
    23        response = requests.get(url,headers = headers)
    24        if response.status_code == 200:
    25            return response.text
    26        return None
    27    except RequestException:
    28        print('爬取失败')
    29
    30
    31def parse_one_page(html):
    32    soup = BeautifulSoup(html,'lxml')
    33    content = soup.select('#myTable04')[0] #[0]将返回的list改为bs4类型
    34    tbl = pd.read_html(content.prettify(),header = 0)[0]
    35    # prettify()优化代码,[0]从pd.read_html返回的list中提取出DataFrame
    36    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)
    37
    38    # print(tbl)
    39    return tbl
    40    # rename将中文名改为英文名,便于存储到mysql及后期进行数据分析
    41    # tbl = pd.DataFrame(tbl,dtype = 'object') #dtype可统一修改列格式为文本
    42
    43def generate_mysql():
    44    conn = pymysql.connect(
    45        host='localhost',
    46        user='root',
    47        password='******',
    48        port=3306,
    49        charset = 'utf8',  
    50        db = 'wade')
    51    cursor = conn.cursor()
    52
    53    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))'
    54    # listed_company是要在wade数据库中建立的表,用于存放数据
    55
    56    cursor.execute(sql)
    57    conn.close()
    58
    59
    60def write_to_sql(tbl, db = 'wade'):
    61    engine = create_engine('mysql+pymysql://root:******@localhost:3306/{0}?charset=utf8'.format(db))
    62    try:
    63        # df = pd.read_csv(df)
    64        tbl.to_sql('listed_company2',con = engine,if_exists='append',index=False)
    65        # append表示在原有表基础上增加,但该表要有表头
    66    except Exception as e:
    67        print(e)
    68
    69
    70def main(page):
    71    generate_mysql()
    72    for i in range(1,page):  
    73        html = get_one_page(i)
    74        tbl = parse_one_page(html)
    75        write_to_sql(tbl)
    76
    77# # 单进程
    78if __name__ == '__main__':    
    79    main(178)
    80
    81    endtime = time.time()-start_time
    82    print('程序运行了%.2f秒' %endtime)
    83
    84
    85# 多进程
    86# from multiprocessing import Pool
    87# if __name__ == '__main__':
    88#     pool = Pool(4)
    89#     pool.map(main, [i for i in range(1,178)])  #共有178页
    90
    91#     endtime = time.time()-start_time
    92#     print('程序运行了%.2f秒' %(time.time()-start_time))
    
    

    最终,A股所有3535家企业的信息已经爬取到mysql中,如下图:
    uploading-image-363968.png

    除了A股,还可以顺便再把港股和新三板所有的上市公司也爬了。后期,将会对爬取的数据做一下简单的数据分析。

    最后,需说明不是所有表格都可以用这种方法爬取,比如这个网站中的表格,表面是看起来是表格,但在html中不是前面的table格式,而是list列表格式。这种表格则不适用read_html爬取。得用其他的方法,比如selenium,以后再进行介绍。

    uploading-image-737366.png

    本文完。

    来源于https://mp.weixin.qq.com/s/kE5LU_8UDPgxv1v4rw9sbA

  • 相关阅读:
    求100-999之间所有的水仙花数
    验证用户密码程序
    【bzoj2002】[Hnoi2010]Bounce 弹飞绵羊 分块/LCT
    【bzoj1070】[SCOI2007]修车 最小费用流
    【bzoj3669】[Noi2014]魔法森林 Kruskal+LCT
    【bzoj3668】[Noi2014]起床困难综合症 贪心
    【bzoj1391】[Ceoi2008]order 网络流最小割
    【bzoj4873】[Shoi2017]寿司餐厅 最大权闭合图
    【bzoj1180】[CROATIAN2009]OTOCI LCT
    【bzoj3282】Tree LCT
  • 原文地址:https://www.cnblogs.com/hankleo/p/9939730.html
Copyright © 2011-2022 走看看