如何批量獲取年報中數據?

最近老師布置任務要獲得四百家公司年報中管理費用明細中的業務招待費和其他費用這兩項數據,有沒有用r或者python可以實現的方法編程批量獲取啊T_T


其實上市公司的年報都是免費的,也有很多網站進行了整理,尤其是對於報表,可以批量獲取。

一、完整版年報獲取方式

通常上市公司會在每年的4月份左右發布上年年報,在該公司的網站上會有下載鏈接,這是最權威的年報獲取手段。

比如安琪酵母:

打開其官方網站,能夠看到「投資者關係」一欄,裡面就是放的各種公告,包括歷年財報。

這裡其實是一個PDF的下載鏈接,使用下載工具下載即可。

如果大量分析年報的話,這樣的下載方式略顯繁瑣。

絕大多數證券網站都做了搜集整理,可以方便的查找下載,比如新浪股票、網易財經、騰訊證券。

我習慣使用騰訊證券,地址在此:http://gu.qq.com/sh600291/gp/jbnb/nos1203336037

敲入股票代碼或者簡寫,就可以下載PDF格式的年報。

二、數字版報表下載方式

通過上文的方式下載的年報,動輒一二百頁,對於深度分析某個公司基本面是有幫助的,但很多時候我們只是想了解一下連續幾年來的某個項目變動情況,F10里不全,PDF一個個看起來會累瞎眼,那怎麼辦呢?

有兩種方式,一種是高昂的國泰安資料庫、Wind客戶端等專業軟體,內置了年報資料,另一種是一些免費的網站,提供了Excel格式的下載鏈接。

比如網易財經。

http://quotes.money.163.com/service/zcfzb_600001.html (股票代碼為600001的資產負債表)

http://quotes.money.163.com/service/lrb_600001.html (股票代碼為600001的利潤表)

http://quotes.money.163.com/service/xjllb_600001.html (股票代碼為600001的現金流量表)

下載的都是格式為csv的Excel表格,為該公司上市以來所有報表。替換掉代碼即可下載你想要的公司的報表。

報表格式如下:

可見利用這些數據進行歷年分析是非常簡單的。

三、一點點編程代碼,批量下載和處理

對於財經數據分析,Python提供了非常友好的處理模式,不過因為我當時在學Ruby,就寫了一段Ruby代碼下載。

作為初學者,很多代碼優化學得不好,在編程高手們面前,這段代碼有點獻醜了。不過幸虧我臉皮厚,既然許多朋友諮詢,我就恬不知恥的放上來了。

# -*- coding: UTF-8 -*-

require "rubygems"

require "hpricot"

require "open-uri"

################################本程序實現財務指定股票上市以來全部財務報表的下載

x = 0

#獲取股票列表文件stocklist.txt的總行數

def wc(filename)

$nline = $nword = $nchar = 0 #$符號表示全局變數,普通變數不在def外起作用

File.open(filename) do |io|

io.each_line do |line|

words = line.split(/s+/).reject{|w| w.empty? }

#本例中使用了split方法分割單詞,當行首有空白字元時,split方法的執行結果中會產生空白字元串,因此我們

#會刪除該空白字元串。

$nline += 1

$nword += words.length

$nchar += line.length

end

end

#puts "文件的行數為:#{$nline}
文件的單詞數為:#{$nword}
文件的字元數為:#{$nchar}"

puts "股票池股票數:#{$nword}
"

end

wc("d:/rb/stock/downreports/stocklist.txt")

#puts $nword

#循環開始

while x &<= $nword - 1

#puts "輪詢中:"

stock_lines = File.readlines("d:/rb/stock/downreports/stocklist.txt");

s_code = stock_lines[x]

scode = s_code.chomp # chomp用來刪除文本裡帶過來的換行符

puts "====================="

puts "正在下載#{scode}的資產負債表,共計#{$nword}只,當前第#{x}只。"

#確定csv文件命名規則

file1_path = "d:\stock\zcfzb"

file1_name = scode + "zcfzb.csv"

file1_name_path = file1_path + file1_name

#將網易財經介面的數據保存為csv文件

File.open(file1_name_path, "wb") {|f| f.write(open("http://quotes.money.163.com/service/zcfzb_" + "#{scode}"+".html") {|f1| f1.read})}

#防止介面調用過頻被踢,暫停3秒

sleep(3)

puts "資產負債表下載完畢"

puts "====================="

puts "正在下載#{scode}的利潤表"

#確定csv文件命名規則

file2_path = "d:\stock\lrb"

file2_name = scode + "lrb.csv"

file2_name_path = file2_path + file2_name

#將網易財經介面的數據保存為csv文件

File.open(file2_name_path, "wb") {|f| f.write(open("http://quotes.money.163.com/service/lrb_" + "#{scode}"+".html") {|f1| f1.read})}

#防止介面調用過頻被踢,暫停3秒

sleep(3)

puts "利潤表下載完畢"

puts "====================="

puts "正在下載#{scode}的現金流量表"

#確定csv文件命名規則

file3_path = "d:\stock\xjllb"

file3_name = scode + "xjllb.csv"

file3_name_path = file3_path + file3_name

#將網易財經介面的數據保存為csv文件

File.open(file3_name_path, "wb") {|f| f.write(open("http://quotes.money.163.com/service/xjllb_" + "#{scode}"+".html") {|f1| f1.read})}

#防止介面調用過頻被踢,暫停3秒

sleep(3)

puts "現金流量表下載完畢"

puts "====================="

puts "正在下載#{scode}的主要財務指標表"

#確定csv文件命名規則

file4_path = "d:\stock\zycwzb"

file4_name = scode + "zycwzb.csv"

file4_name_path = file4_path + file4_name

#將網易財經介面的數據保存為csv文件

File.open(file4_name_path, "wb") {|f| f.write(open("http://quotes.money.163.com/service/zycwzb_" + "#{scode}"+".html") {|f1| f1.read})}

#防止介面調用過頻被踢,暫停3秒

sleep(3)

puts "主要財務指標表下載完畢"

x = x + 1

end

本段代碼的核心就是這一行了:

f.write(open("http://quotes.money.163.com/service/zcfzb_" + "#{scode}"+".html") {|f1| f1.read})}

學過一丁點代碼的,應該一看就明白了。此處雖然用的Ruby,你換Python也很容易。

下載完畢後,還是很壯觀的:

有了這些基礎數據,你就可以隨意分析任意上市公司的歷年財報了。

和國泰安資料庫、Wind數據相比,起碼省了幾萬塊,還不趕快去加個雞腿?


題主問題不清晰,簡單的回答一下思路,最關鍵的點在於:程序只能按照你的思路做機械的批量化的操作,如果整個過程中有隻有人能夠識別操作的行為(比如年報地址分散得找,年報格式不統一得去識別等),那你就只能手工去做了。如果其中有重複的模式,那可以考慮編程解決。

  1. 年報已經有了文件,還是得去找?
  • 如果沒有,得去下載的話,有沒有有規律的可以機械化處理的方式(比如某個網站你提供名稱就能下載到)?沒有的話請放棄。
  • 年報格式是否統一?比如你要的格式是否以固定的格式出現在某個位置?
  • 去編程提取信息的操作難度和手工去找的工作量對比如何?該程序的可復用性如何?如果這個功能就用這一次,而且編出來比手動操作還麻煩,那當然不用費這個心了。
  • 如果你能夠描述出來怎樣用固定的步驟解決這個問題,那你可以考慮找個懂編程的人來解決。如果自己都說不清怎麼操作,那就別浪費時間了,自己慢慢干吧。


    @胡文超 的回答是典型程序員思路。一般business思路是:打開Bloomberg,直接提取,如果不會做的話,按兩下鍵盤上的help問在線客服,或者打電話給客服。也可以考慮用wind,只支援大陸股票,也有相關信息提取功能。


    最高票 @路過銀河 說的沒啥錯,不過文不對題沒啥卵用啊。。題目需要的是年報中的非結構化數據啊親。。三表裡可是找不到的啊(附註里才有)。。。

    批量提取400多家的話,肯定得用程序,這個不是難事。難點在於如何把附註里的這些非結構化信息提取出來。

    當前有些公司做的就是這個,賣出去應該還是挺值錢的。比如我所知道的文因互聯做的就是這一塊,從看到的情況來說,他們是具備這個技術的,實現的效果也挺不錯。實現過程參見其申請的專利(當然沒有太具體的技術細節)。

    具體細節的話,大概要先考慮年報的組織形式。年報一般都是pdf的,但是(這個但是很重要啊!)還有些網站上放出了非pdf版本,一般是html頁面。

    html頁面就好辦了,直接(1)寫爬蟲(2)正則解析需要的部分(3)完事收工。

    【提示:東財、新浪一般都有html頁面】

    非要用pdf也不是沒辦法,先爬蟲下載所有pdf文件,再用能解析pdf的庫。比如python里就有好多,pytable之類(參見關於此問題的知乎問答),然後就可以了,中間有些坑就自己慢慢填吧。

    【知乎編輯器真是爛成shit啊啊啊啊】


    好像滬深的都有了,我來個新三板

    的吧。

    代碼沒有優化,因為懶。。。慎重

    #!/usr/bin/python3
    # -*- coding: utf-8 -*-

    import requests
    import json
    from bs4 import BeautifulSoup
    import xlsxwriter
    import re

    def getURl(anysheet):
    u = "http://xinsanban.eastmoney.com/api/F10/Finance/" + anysheet
    return u

    #定義一個函數用於獲取json數據
    def getHTMLjson(url,stocknum):
    #codeAndType是向鏈接傳遞的表單,datetype=0是默認按半年報來的,datetype=6是按年報
    codeAndType = {"MSECUCODE":stocknum,"dateType":"0"}
    try:
    r = requests.get(url,params=codeAndType)
    r.raise_for_status()
    r.encoding = r.apparent_encoding
    return r.json() #返回json數據格式
    except:
    return "爬取失敗"

    #定義兩個函數將原始的json數據中我們需要的部分取出來
    #第一個函數取中文標題
    def getTitle(jsondata):
    title = {} #l是一個空字典,用於存儲中英文標題欄
    for key in jsondata["Trs"]:
    title[str(key["Field"])] = key["Name"]
    return title

    #第二個函數取報表數據
    def getYearsData(jsondata):
    yearsData = [] #ls是一個空列表,用於存儲三年的數據
    for k in jsondata["result"]:
    yearsData.append(k)
    return yearsData

    #fixedData函數將標題和數據整理好
    def fixedData(title,yearsData):
    ds = []
    count = 0
    def exangeData(i):#構造這個函數用於將title,years的數據整合起來
    for a,b in title.items():
    for c,d in yearsData[i].items():
    if a == c: #利用英文標題欄將需要的數據整合成字典
    dic[b] = d
    return dic
    while True:
    dic = {}
    y = exangeData(count)
    ds.append(y)
    count += 1
    if count == len(yearsData):
    break
    return ds

    #finaData調用前面的函數生成最終的數據
    def finalData(sheet,code):
    url = getURl(sheet)
    YsData = getHTMLjson(url,code)
    titles = getTitle(YsData)
    datas = getYearsData(YsData)
    s = fixedData(titles,datas)
    return s

    def main(code):
    BS = finalData("zcfzb",code)
    CFS = finalData("xjllb",code)
    PL = finalData("lrb",code)
    alldata = [BS,CFS,PL]
    return alldata

    #writContent函數將內容寫進入
    def writeContent(workbook,x,sheetName):
    worksheet = workbook.add_worksheet(sheetName)
    #將A1處設置為空白
    worksheet.write("A1", "")
    i = 0
    j = 66
    while True:
    worksheet.write(chr(j)+"1", str(list(s[x][i].values())[0]))
    i += 1
    j += 1
    if i == len(s[x]):
    break
    m = 2
    #製作左側的科目
    while True:
    worksheet.write("A"+str(m),str((list(s[x][0]))[m]))
    m +=1
    if m == len(s[x][0]):
    break
    #oneLine函數一行一行地往下寫
    def oneLine(worksheet,x,y):
    e = 0
    f = 66
    while True:
    worksheet.write(chr(f)+str(y), str(list(s[x][e].values())[y]))
    e += 1
    f += 1
    if e == len(s[0]):
    break
    return worksheet
    x = x
    y = 2
    while True:
    oneLine(worksheet,x,y)
    y += 1
    if y == len(s[x][0]):
    break

    def writeAllSheets(stockcode):
    s = main(stockcode)
    workbook = xlsxwriter.Workbook(str(stockcode) + ".xlsx")
    sheetNameList = ["BS","CFS","PL"]
    g = 0
    while True:
    writeContent(workbook,g,sheetNameList[g])
    g += 1
    if g == len(sheetNameList):
    break
    workbook.close()

    def getAstock(stockcode):
    try:
    writeAllSheets(stockcode)
    except:
    pass

    #以下函數為獲取所有股票的列表
    def getStockCodeText(url,page):
    try:
    r = requests.get(url,params=page)
    r.raise_for_status()
    r.encoding = r.apparent_encoding
    return r.text
    except:
    return "爬取失敗"

    def getStockCodeList(html):
    lst = []
    soup = BeautifulSoup(html,"html.parser")
    a = soup.find_all("a")
    for i in a:
    try:
    title = i.attrs["title"]
    lst.append(re.findall(r"d{6}", title)[0])
    except:
    continue
    return lst

    def endPage():
    try:
    r = requests.get("http://www.jingujie.com/stocklist/")
    r.raise_for_status()
    r.encoding = r.apparent_encoding
    html = r.text
    except:
    return "爬取失敗"
    pages = []
    soup = BeautifulSoup(html,"html.parser")
    a = soup.find_all("a")
    for i in a:
    try:
    page = i.string
    pages.append(re.findall(r"d{3}", page)[0])
    except:
    continue
    return pages

    def finalCodeList():
    finalpages = endPage()[-1]
    clst = []
    v = 1
    url = "http://www.jingujie.com/stocklist/"
    while True:
    vd = {"page":v}
    htmlText = getStockCodeText(url,vd)
    c = getStockCodeList(htmlText)
    for j in c:
    clst.append(j)
    v += 1
    if v == 2:
    break
    return clst

    k = finalCodeList()
    r = 0
    while True:
    s = main(k[r])
    getAstock(k[r])
    r += 1
    if r == len(k):
    break

    這是用作測試的,真正要抓取所有新三版上市公司數據的話,把倒數第十一行的v ==2 改成v ==len(finalpages)就可以了,不過。。數據很多,測算了下有12240隻股票。。。

    有個抓幾隻的版本,懶得貼了。。。

    輕噴。。。

    抓下來大概是這個樣子

    單個文件


    csmar以及ccer資料庫里收錄了財報附註中披露的管理費用明細項,可以下載到~


    國內土鱉怒答,有錢買萬德,沒錢python,門戶網站上經常有全文,某些蛋疼的傢伙上傳掃描件的,肉眼吧,呵呵


    推薦閱讀:

    Python的大數運算到底是根據什麼基礎原理或者演算法實現的?
    將來 UWP 會不會支持 python?
    哪些簡單的linux或者python技能,能直接用在生活上讓周圍人刮目相看?
    就目前就業形勢和今後發展 PHP和Python作為後台開發語言哪一個更合適?
    為什麼python缺少一個msbuild,從2008年到2014年一直不加上?

    TAG:Python | 財務 | R編程語言 |