6小時爬完上交所和深交所的年報問詢函

“沒有槍沒有炮我們給自己造。”

—— 周書人

昨天開組會的時候導師說想搞年報問詢函的研究,但是買數據庫太貴了。我說放着我來 ( ‵▽′)ψ。

一、任務描述

  • 分別從上交所和深交所的官網上爬取年報問詢函的記錄

二、解決思路

  • 解析網頁獲取全部的年報問詢函列表及相應的文件鏈接
  • 打開第一步獲取的文件鏈接,讀取 PDF 數據,並直接轉成 TXT 格式的文字

三、網頁分析

以上交所網站爲例:

1. 數據包位置深交所網頁

上交所的年報問詢列表存儲在響應的一個 JSON 文件裏,只要用 Python3 發送請求並截取這個 JSON 包就可以直接獲取該頁的列表啦。

2. 翻頁

由於網站結構簡單,所以翻頁也很好實現。對比第一頁的參數和第二頁的參數可以發現:翻頁的關鍵在於 pageHelp.pageNopageHelp.beginPage 這兩個參數上,所以雖然一共有 88 頁的問詢函,只要每次 post 不同的頁數就可以得到相應的頁面。
第一頁參數第二頁參數

3. 下載PDF

通過前兩步的分析我們已經拿到了上交所問詢函的所有列表:
上交所列表
顯然上交所網站裏已經直接給出了每個問詢函 PDF 的下載鏈接(即表格最後一列),直接打開這個鏈接就可以讀取保存相應的問詢函。

4. 深交所網頁和上交所網頁的區別

深交所網站比較好的是直接給出了問詢函列表並且同時給出了函件內容和公司回覆文檔的文件編碼:
深交所問詢函列表
舉個栗子:加入函件內容的文件編碼是 CDD00080753986.pdf ,那麼給這個編碼套上外衣就是可以打開的 PDF 地址了:
http://reportdocs.static.szse.cn/UpFiles/fxklwxhj/ + 文件編碼+ ?random=0.42680171432249325
最後得到的URL地址爲:http://reportdocs.static.szse.cn/UpFiles/fxklwxhj/CDD00080753986.pdf?random=0.42680171432249325
小夥伴們可以試試打開是不是正確的問詢函件。

四、PDF轉TXT

Python3 裏用經典的 Pdfminer3k 包即可解決,這裏直接改了大佬的代碼ᕕ( ᐛ )ᕗ:Pythonscrapy的簡書:python3 在線讀取pdf

五、核心代碼

1. 獲取上交所問詢函列表

本來應該規規矩矩地用 Requests 包的 post 方法上傳參數的,但是我太懶了啊哈哈哈就直接迭代了醜醜的 URL。

import requests

def downlourl(currentpage):
    url = "http://query.sse.com.cn/commonSoaQuery.do?siteId=28&sqlId=BS_GGLL&extGGLX=&stockcode=&channelId=10743%2C10744%2C10012&extGGDL=&order=createTime%7Cdesc%2Cstockcode%7Casc&isPagination=true&pageHelp.pageSize=15&pageHelp.pageNo=" + repr(currentpage) + "&pageHelp.beginPage=" + repr(currentpage) +"&pageHelp.cacheSize=1"
    return(url)

headers = {
    'Referer':'http://www.sse.com.cn/disclosure/credibility/supervision/inquiries/',
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
}

with open('上交所列表.txt',"a") as f:
    for page in range(89):
        r = requests.get(downlourl(page), headers=headers)
        for i in r.json()['result']:
            f.write('\t''.join([i['cmsOpDate'],i['docTitle'],i['stockcode'],i['extWTFL'],i['extGSJC'],i['docType'],i['createTime'],i['docURL']])+'\n')
        print('完成爬取第%d頁'%page)  

2. 爬取PDF並直接轉爲TXT

import pandas as pd
from urllib.request import urlopen
from urllib.request import Request

from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTTextBoxHorizontal, LAParams
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.pdfinterp import PDFTextExtractionNotAllowed
from pdfminer.pdfparser import PDFParser, PDFDocument


url = "http://www.cninfo.com.cn/new/hisAnnouncement/query"
data = pd.read_table('深交所年報問詢函列表.txt',header=0,encoding='utf8',delim_whitespace=True)
data.columns=['函件內容',	'公司回覆']


函件內容 = data.loc[:,'函件內容']
公司回覆 = data.loc[:,'公司回覆']


headers = {'content-type': 'application/json',
           'Accept-Encoding': 'gzip, deflate',
           'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0'}

baseurl = "http://reportdocs.static.szse.cn/UpFiles/fxklwxhj/"

def parse(docucode):
    # 打開在線PDF文檔
    _path = baseurl + docucode + "?random=0.3006649122149502"
    request = Request(url=_path, headers=headers)  # 隨機從user_agent列表中抽取一個元素
    fp = urlopen(request)
    # 讀取本地文件
    # path = './2015.pdf'
    # fp = open(path, 'rb')
    # 用文件對象來創建一個pdf文檔分析器
    praser_pdf = PDFParser(fp)
    # 創建一個PDF文檔
    doc = PDFDocument()
    # 連接分析器 與文檔對象
    praser_pdf.set_document(doc)
    doc.set_parser(praser_pdf)
    # 提供初始化密碼doc.initialize("123456")
    # 如果沒有密碼 就創建一個空的字符串
    doc.initialize()
    # 檢測文檔是否提供txt轉換,不提供就忽略
    if not doc.is_extractable:
        raise PDFTextExtractionNotAllowed
    else:
        # 創建PDf資源管理器 來管理共享資源
        rsrcmgr = PDFResourceManager()
        # 創建一個PDF參數分析器
        laparams = LAParams()
        # 創建聚合器
        device = PDFPageAggregator(rsrcmgr, laparams=laparams)
        # 創建一個PDF頁面解釋器對象
        interpreter = PDFPageInterpreter(rsrcmgr, device)
        # 循環遍歷列表,每次處理一頁的內容
        # doc.get_pages() 獲取page列表
        for page in doc.get_pages():
            # 使用頁面解釋器來讀取
            interpreter.process_page(page)
            # 使用聚合器獲取內容
            layout = device.get_result()
            # 這裏layout是一個LTPage對象 裏面存放着 這個page解析出的各種對象 一般包括LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等等 想要獲取文本就獲得對象的text屬性,
            for out in layout:
                # 判斷是否含有get_text()方法,圖片之類的就沒有
                # if hasattr(out,"get_text"):
                docname = str(docucode)[:-4]+'.txt'
                with open(docname,'a') as f:
                    if isinstance(out, LTTextBoxHorizontal):
                        results = out.get_text()
                        print(results)
                        f.write(results)


for i in range(len(公司回覆)):
    try:
        parse(函件內容[i])
        print(repr(函件內容[i]) + "爬取成功")
    except:
        print(repr(函件內容[i]) + "爬取不成功")
    try:
        parse(公司回覆[i])
        print(repr(公司回覆[i]) + "爬取成功")
    except:
        if repr(公司回覆[i]) == "nan":
            print("無公司回覆")
        else:
            print(repr(公司回覆[i]) + "爬取不成功")

3. 遍歷文件夾中所有TXT文件並生成列表

參考了 Lynn大神的博客 |・ω・`)

import os
import pandas as pd

content_list = []

def readFile(filepath):
    content = open(filepath, "r").read()     #打開傳進來的路徑
    docucode = filepath.split('/')[-1]
    content_list.append([docucode.split('.')[0],content])
    print([docucode.split('.')[0],content])


def eachFile(filepath):
    pathDir = os.listdir(filepath)      #獲取當前路徑下的文件名,返回List
    for s in pathDir:
        newDir=os.path.join(filepath,s)     #將文件命加入到當前文件路徑後面
        if os.path.isfile(newDir) :         #如果是文件
            if os.path.splitext(newDir)[1]==".txt":  #判斷是否是txt
                readFile(newDir)
                pass
        else:
            eachFile(newDir)                #如果不是文件,遞歸這個文件夾的路徑


eachFile("/Users/上交所txt/")

df = pd.DataFrame(content_list, columns=['docucode', 'content'])
df.to_csv("上交所問詢函內容.csv", sep=',', mode='w',encoding='gb18030')

六、最終爬取結果

最終爬取列表

主要參考鏈接

  1. https://www.cnblogs.com/SeekHit/p/6245283.html
  2. http://www.imooc.com/wenda/detail/432379
  3. https://www.jianshu.com/p/42caf169764b
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章