摘要: 我们平常在浏览网页中会遇到一些表格型的数据信息,除了表格本身体现的内容以外,可能还想透过表格背后再挖掘些有意思或者有价值的信息。这时,可用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. 1<table class="..." id="...">
  2. 2 <thead>
  3. 3 <tr>
  4. 4 <th>...</th>
  5. 5 </tr>
  6. 6 </thead>
  7. 7 <tbody>
  8. 8 <tr>
  9. 9 <td>...</td>
  10. 10 </tr>
  11. 11 <tr>...</tr>
  12. 12 <tr>...</tr>
  13. 13 <tr>...</tr>
  14. 14 <tr>...</tr>
  15. 15 ...
  16. 16 <tr>...</tr>
  17. 17 <tr>...</tr>
  18. 18 <tr>...</tr>
  19. 19 <tr>...</tr>
  20. 20 </tbody>
  21. 21</table>

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

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

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

2. 快速抓取

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

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

uploading-image-806178.png

只需不到十行代码,1分钟左右就可以将全部178页共3535家A股上市公司的信息干净整齐地抓取下来。比采用正则表达式、xpath这类常规方法要省心省力地多。如果采取人工一页页地复制粘贴到excel中,就得操作到猴年马月去了。

上述代码除了能爬上市公司表格以外,其他几个网页的表格都可以爬,只需做简单的修改即可。因此,可作为一个简单通用的代码模板。但是,为了让代码更健壮更通用一些,接下来,以爬取177页的A股上市公司信息为目标,讲解一下详细的代码实现步骤。

3. 详细代码实现

3.1. read_html函数

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

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

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

3.4. 存储到MySQL

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

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

  1. 1 import pymysql
  2. 2
  3. 3 def generate_mysql():
  4. 4 conn = pymysql.connect(
  5. 5 host='localhost', # 本地服务器
  6. 6 user='root',
  7. 7 password='******', # 你的数据库密码
  8. 8 port=3306, # 默认端口
  9. 9 charset = 'utf8',
  10. 10 db = 'wade')
  11. 11 cursor = conn.cursor()
  12. 12
  13. 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. 14 # listed_company是要在wade数据库中建立的表,用于存放数据
  15. 15
  16. 16 cursor.execute(sql)
  17. 17 conn.close()
  18. 18
  19. 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. 1 import pymysql
  2. 2 from sqlalchemy import create_engine
  3. 3
  4. 4 def write_to_sql(tbl, db = 'wade'):
  5. 5 engine = create_engine('mysql+pymysql://root:******@localhost:3306/{0}?charset=utf8'.format(db))
  6. 6 # db = 'wade'表示存储到wade这个数据库中,root后面的*是密码
  7. 7 try:
  8. 8 tbl.to_sql('listed_company',con = engine,if_exists='append',index=False)
  9. 9 # 因为要循环网页不断数据库写入内容,所以if_exists选择append,同时该表要有表头,parse_one_page()方法中df.rename已设置
  10. 10 except Exception as e:
  11. 11 print(e)

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

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

10行代码爬取全国所有A股/港股/新三板上市公司信息的更多相关文章

  1. 40行代码爬取猫眼电影TOP100榜所有信息

    主要内容: 一.基础爬虫框架的三大模块 二.完整代码解析及效果展示 1️⃣  基础爬虫框架的三大模块 1.HTML下载器:利用requests模块下载HTML网页. 2.HTML解析器:利用re正则表 ...

  2. 80 行代码爬取豆瓣 Top250 电影信息并导出到 CSV 及数据库

    一.下载页面并处理 二.提取数据 观察该网站 html 结构 可知该页面下所有电影包含在 ol 标签下.每个 li 标签包含单个电影的内容. 使用 XPath 语句获取该 ol 标签 在 ol 标签中 ...

  3. 33行代码爬取妹子图片(bs4+urllib)

    from bs4 import BeautifulSoupimport urllib2import urllibimport lxmlimport os def get_imgs(): image_c ...

  4. python爬虫学习之爬取全国各省市县级城市邮政编码

    实例需求:运用python语言在http://www.ip138.com/post/网站爬取全国各个省市县级城市的邮政编码,并且保存在excel文件中 实例环境:python3.7 requests库 ...

  5. 如何用Python统计《论语》中每个字的出现次数?10行代码搞定--用计算机学国学

    编者按: 上学时听过山师王志民先生一场讲座,说每个人不论干什么,都应该学习国学(原谅我学了计算机专业)!王先生讲得很是吸引我这个工科男,可能比我的后来的那些同学听课还要认真些,当然一方面是兴趣.一方面 ...

  6. 《zw版·Halcon-delphi系列原创教程》简单的令人发指,只有10行代码的车牌识别脚本

    <zw版·Halcon-delphi系列原创教程>简单的令人发指,只有10行代码的车牌识别脚本 简单的令人发指,只有10行代码的车牌识别脚本      人脸识别.车牌识别是opencv当中 ...

  7. [Unity Editor]10行代码搞定Hierarchy排序

    在日常的工作和研究中,当给我们的场景摆放过多的物件的时候,Hierarchy面板就会变得杂乱不堪.比如这样:    过多的层次结构充斥在里面,根层的物件毫无序列可言,整个层次面板显示非常的杂乱不堪,如 ...

  8. 10行代码搞定移动web端自定义tap事件

    发发牢骚 移动web端里摸爬滚打这么久踩了不少坑,有一定移动web端经验的同学一定被click困扰过.我也不列外.一路走来被虐的不行,fastclick.touchend.iscroll什么的都用过, ...

  9. delphi 牛逼 了 app (已在软件界掀起波澜)10分钟10行代码做出让人惊叹的程序

    (已在软件界掀起波澜)10分钟10行代码做出让人惊叹的程序 http://v.qq.com/x/page/m0328h73bs7.html?ptag=bbs_csdn_net

随机推荐

  1. 给RadioButtonList绑定Selected的值

    有一个案例,是读取Excel的资料显示于ASP.NET的GridView控件.在GridView控件中,有一列是用RadioButtonList来显示性别信息(男或女). 另外来看看Excel的数据: ...

  2. 【ZeroMQ】2、高性能的通讯库-zeroMQ

    首先,让我来介绍一下什么是ZMQ(全称:ZeroMQ): 官方: “ZMQ(以下ZeroMQ简称ZMQ)是一个简单好用的传输层,像框架一样的一个socket library,他使得Socket编程更加 ...

  3. mongodb在线web管理工具

    随着云计算,大数据等技术的不断发展,需要服务应用都朝着网络化,在线化的方向演进,数据库管理,数据库维护,数据可视化等也是这种趋势.MonggoDB,MySQL的在线管理,已成为一种强烈的需求,使用Tr ...

  4. Codeforces35E(扫描线)

    E. Parade time limit per test:2 seconds memory limit per test:64 megabytes input:input.txt output:ou ...

  5. Hadoop Mapreduce 参数 (二)

    MergeManagerImpl 类 内存参数计算 maxInMemCopyUse 位于构造函数中 final float maxInMemCopyUse = jobConf.getFloat(MRJ ...

  6. js飘窗

    广告页上总会出现飘窗效果: adver_pos_id = getOtherParameter("id"); adver_Sid = getOtherParameter(" ...

  7. 关于flex布局兼容

    (做个记录) 一.W3C各个版本的flex 2009 version 标志:display: box; or a property that is box-{*} (eg. box-pack) 201 ...

  8. CSS应用的小问题总结

    1.两个元素换行书写时,在实际的布局中展示为两个元素之间多了一个区间(这个区间通常是因为代码在换行时,解析会自动默认为一个空格字符),所以在实际应用时,如果想要将两个元素完全无缝隙的放置在一起并排显示 ...

  9. ionic1 项目微信支付

    使用的插件参照地址:https://github.com/xu-li/cordova-plugin-wechat:(这里包含微信登录,微信分享和微信支付) 插件安装 cordova plugin ad ...

  10. profile,bashrc,.bash_profile,.bash_login,.profile,.bashrc,.bash_logout浅析 Part 2

    profile,bashrc,.bash_profile,.bash_login,.profile,.bashrc,.bash_logout浅析 Part 2   by:授客 QQ:103355312 ...