Python 爬虫(Selenium+lxml)

爬虫 东方财富网

 

界面和network信息先贴上。我们需要实现获得该页面的资产负债数据,并能指定code切换企业。

这里还有Python js执行,我们先安装一个selenium。selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题。安装命令:conda install selenium

然后分析检查这张页面的源码,找到我们需要的资产负债表。我这里提取了一下,结构如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>

    <meta charset="utf-8"/>
    ...
    <title>东方财富(300059.SZ)新财务分析-PC_HSF10资料</title>
    ...
</head>
<body>
...
<div class="main">
    ...
    <div id="divBody">
        ...
        <div class="section">
            ...
            <div class="content" style="text-align:center;">
                ...
                <i class="prev" id="zcfzb_prev" style="display: none;"></i>
                <i class="next" id="zcfzb_next" style="display: inline;"></i>
                <div class="tab tips-fontsize">
                    <ul id="zcfzb_ul">
                        <li class="first current" reportdatetype="0" reporttype="1">
                            <span>按报告期</span>
                        </li>
                        ...
                    </ul>
                </div>
                <table id="report_zcfzb" style="table-layout: fixed;">
                    <tr>
                        <td>
                            <img src="/Content/SoftImages/loading.gif" alt="">
                        </td>
                    </tr>
                </table>
            </div>
        </div>
        ...
    </div>
    ...
</div>
</body>
</html>

检查html可以发现我们要爬取的资产负债数据表,对应的元素id就是report_zcfzb。这里有一个问题。就是这个table的数据是由源码中的js生成的,直接的静态页面用了一个图片占位。我们就需要用到selenium的wait方法,在表格行列生成后再获得并解析。按上一篇的步骤我们来分布进行爬虫。

1.发起请求

我们使用selenium的webdriver来实现。早期的selenium结合PhantomJS来进行相关js操作,但是新版本的selenium已经不支持PhantomJS,改用Chrome或Firefox的无头版本来替代。你可以降低selenium版本,或直接使用无头模式。安装selenium命令:conda install selenium 。请求部分功能代码如下:

chrome_options = Options()
chrome_options.add_argument('--disable-gpu')  # 谷歌文档提到需要加上这个属性来规避bug
chrome_options.add_argument('--hide-scrollbars')  # 隐藏滚动条, 应对一些特殊页面
chrome_options.add_argument('blink-settings=imagesEnabled=false')  # 不加载图片, 提升速度
chrome_options.add_argument('--headless')  # 浏览器不提供可视化页面
driver = webdriver.Chrome(executable_path='D:\software\chromedriver2.3.2\chromedriver.exe', options=chrome_options)
driver.get(url + "?code=" + code)
wait = WebDriverWait(driver, 15)
        wait.until(
            EC.presence_of_element_located(
                (By.XPATH, '//table[@id="report_zcfzb"]//th[@class="tips-colname-Left"]')))  # wait方法 等待table下th 出现
        

以上使用的是Chrome无头模式(headless)。其实就是模拟Chrome浏览器访问。如果不增加options,运行代码会打开Chrome浏览器(这种方式可以用来界面操作登陆等)。

需要注意的部分是executable_path,表示对webdriver指定chromedriver。你需要去下载当前Chrome浏览器对应版本的driver,下载地址为:http://chromedriver.storage.googleapis.com/index.html。每个版本对应不同版本的driver,对应关系百度了一个图,如下:

我的Chrome版本是60,开始下载的2.33报错,后来用的2.32通过。driver下载到本地一般为压缩包,解压至选中路径,将代码中的executable_path指向chromedriver.exe即可。

到了driver.get(url)这步,表示开始执行请求。接下来使用的WebDriverWait,则是用来实现篇前我们说的js未执行,表格数据未加载完成的问题,引用出处为:from selenium.webdriver.support.wait import WebDriverWait。这个资产负债的表格加载完成的结构如下:

<table id="report_zcfzb" style="table-layout: fixed;">
    <tbody>
    <tr>
        <th class="tips-colname-Left" style="width: 366px;"><span>资产负债表</span></th>

        <th><span>2019-09-30</span></th>

        <th><span>2019-06-30</span></th>

        <th><span>2019-03-31</span></th>

        <th><span>2018-12-31</span></th>

        <th><span>2018-09-30</span></th>

    </tr>
    <tr>
        <td class="tips-fieldname-Left" style="font-weight:bold;"><span>流动资产</span></td>

        <td class="tips-data-Right"><span></span></td>

        <td class="tips-data-Right"><span></span></td>

        <td class="tips-data-Right"><span></span></td>

        <td class="tips-data-Right"><span></span></td>

        <td class="tips-data-Right"><span></span></td>

    </tr>
    <tr>
        <td class="tips-fieldname-Left"><span>    货币资金</span></td>

        <td class="tips-data-Right"><span>191.9亿</span></td>

        <td class="tips-data-Right"><span>218.6亿</span></td>

        <td class="tips-data-Right"><span>290.6亿</span></td>

        <td class="tips-data-Right"><span>113.3亿</span></td>

        <td class="tips-data-Right"><span>107.5亿</span></td>

    </tr>
    ...
    </tbody>
</table>

wait = WebDriverWait(driver, 15)
        wait.until(
            EC.presence_of_element_located(
                (By.XPATH, '//table[@id="report_zcfzb"]//th[@class="tips-colname-Left"]')))

这两句代码 ,表示在 id为report_zcfzb的table下,class为tips-colname-Left的th元素未出现之前,等待15s。EC的引用出处是:from selenium.webdriver.support import expected_conditions as EC,这个下面还有其他一些方法,如visibility_of_element_located:判断某个元素是否可见,详细的可以自己研究。这里不再赘述。

执行到这里,我们就获得了如上图结构的table数据,如果需要查看,可以使用open方法保存html到本地查看。

2.获取响应内容

这没什么特殊的,发出请求成功后,使用driver.page_source就获得了返回数据。当然,如果可以,自己添加一个超时、网络判断更好。

3.解析内容

这里我们使用lxml,你也可以使用re正则、BeautifulSoup库、PyQuery库等。lxml+xpath相对来说解析速度快一点。

引用加上:from lxml import etree。获得dom结构内容,html = etree.HTML(driver.page_source)

然后分析截图里table和整体dom的结构,我们把xpath的路径写好,并进行遍历取值。

xpath的常用规则

(1)直接使用节点名称:选取当前节点的所有子节点
(2)/:从根节点开始选取(绝对路径)
(3)//:从匹配到的节点选取(相对路径)
(4).:选取当前节点
(5)..:选取当前节点的父节点

这是一个列表和字典的结合,输出结构应该为:

[{"name": 货币资金, "2019-09-30": "191.9亿", "2019-06-30": "218.6亿", "2019-03-31": "290.6亿", ......"group": “流动资产"},.....]

接下来我们进行解析,先获得表头,xpath应该为//table[条件]//tr/th

table = html.xpath("//table[@id='report_zcfzb']")[0]  # xpath得到的是数组
trlist = table.xpath(".//tr")  # table节点子孙目录下所有行
tdheads = trlist[0].xpath("./th")  # 获得表头所有列 tr/th/

遍历tdheads,插入list。并插入第一个name,用来表示接下来行第一列名称。

然后从第一行开始遍历,获得大分类,大分类按照style条件获得。存入变量group;

tdspecial = tr.xpath("./td[@style='font-weight:bold;']")
group = tdspecial[0].xpath("./span/text()")[0].strip()

获得分类下每行数据,存入字典。

ths = tr.xpath("./td")
data[item] = ths[i].xpath("./span/text()")[0].strip()

strip是去除前后空格。

最终我们得到一个列表list。

4.保存数据

表格数据常见的是保存数据库,保存json字符串。数据库暂时不做,我们存入csv和json文件。

引用csv和json,都是Python自带的包,不用安装。

保存csv文件:这里的headarr为表头列表,list为多条字典列表,表头必须和字典一一对应。

with open('zcfz.csv', 'w', encoding='utf-8', newline='')as f:
    f_csv = csv.DictWriter(f, headarr)
    f_csv.writeheader()
    f_csv.writerows(list)

保存json文件

with open('zcfz.json', 'w', encoding='utf-8') as fp:
    json.dump(res2, fp, ensure_ascii=False)

到这里我们就完成了对财富网的资产负债表的爬虫,切换code,增加输入即可。

完整代码贴上

import os

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from lxml import etree
import csv  # csv存储
import json  # json存储


# 该页面 表格数据由js生成 需要1、等待js执行结束爬页面元素(本例子) 2、直接调用js得到数据

def spider(url, code, save):
    chrome_options = Options()
    chrome_options.add_argument('--disable-gpu')  # 谷歌文档提到需要加上这个属性来规避bug
    chrome_options.add_argument('--hide-scrollbars')  # 隐藏滚动条, 应对一些特殊页面
    chrome_options.add_argument('blink-settings=imagesEnabled=false')  # 不加载图片, 提升速度
    chrome_options.add_argument('--headless')  # 浏览器不提供可视化页面
    driver = webdriver.Chrome(executable_path='D:\software\chromedriver2.3.2\chromedriver.exe', options=chrome_options)
    try:
        driver.get(url + "?code=" + code)
        wait = WebDriverWait(driver, 15)
        wait.until(
            EC.presence_of_element_located(
                (By.XPATH, '//table[@id="report_zcfzb"]//th[@class="tips-colname-Left"]')))  # wait方法 等待table下th 出现
        # with open('b.html', 'w', encoding='utf-8') as f:
        #     f.write(driver.page_source) # 得到js执行后的text
        html = etree.HTML(driver.page_source)
        table = html.xpath("//table[@id='report_zcfzb']")[0]  # xpath得到的是数组
        # print(table.xpath('@style'))  # ['table-layout: fixed;'] 到这一步得到table目录
        trlist = table.xpath(".//tr")  # table节点子孙目录下所有行
        tdheads = trlist[0].xpath("./th")  # 获得表头所有列 tr/th/
        # tdheads = trlist[0].xpath("./th[position()>1]")  # 获得表头所有列 跳过第一个列
        d = 0;
        headarr = []
        for head in tdheads:
            headarr.append(head.xpath("./span/text()")[0].strip())
            d += 1
        headarr[0] = 'name'
        print('一共列数:', d)  # 一共列
        # print(headarr)
        # ***********************遍历数据行**********************************
        row = 0
        list = []
        group = ''
        for tr in trlist:
            if row > 0:  # 跳过第一行
                # 处理font-weight:bold; 大分类节点
                tdspecial = tr.xpath("./td[@style='font-weight:bold;']")  # 第一列加粗表示为大分类这行
                data = {}
                if (len(tdspecial) != 0):
                    group = tdspecial[0].xpath("./span/text()")[0].strip()
                    continue
                ths = tr.xpath("./td")  # 获得当前行所有列 tr/td/
                i = 0
                for item in headarr:
                    data[item] = ths[i].xpath("./span/text()")[0].strip()
                    i += 1;
                data['group'] = group
                # print(data)
                list.append(data)
            row += 1
        print('一共行数:', row)  # 一共行
        res2 = json.dumps(list)
        headarr.append('group')
        if save == 1:
            with open('zcfz.json', 'w', encoding='utf-8') as fp:
                json.dump(res2, fp, ensure_ascii=False)  # 保存json文件
        elif save == 2:
            with open('zcfz.csv', 'w', encoding='utf-8', newline='')as f:
                f_csv = csv.DictWriter(f, headarr)
                f_csv.writeheader()
                f_csv.writerows(list)
        print("保存成功")
    # except:
    #     print("error!!")
    finally:
        driver.close()  # 切记关闭浏览器,回收资源


if __name__ == '__main__':
    code = input("请输入code:")
    save = int(input("请输入保存方式 json(1)/csv(2):"))  #
    print("*****************保存文件请在本地文件夹查看*************************")
    url = 'http://emweb.securities.eastmoney.com/NewFinanceAnalysis/Index'
    spider(url, code, save)
    os.system("pause")  # 使程序发布后可见

# 打包exe 安装 pyinstaller conda install pyinstaller
# 打包pyinstaller -F Finance.py 先打开cmd, 再输入"xxx.exe"执行

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值