三小时爬取四万份年报
本文爬虫的任务提交速度以及下载速度:
年报文本分析
如果你有年报文本分析需求,首先你就要获取上市公司年报
关于上市公司年报的爬虫已经有很多篇了,如下:
《30行代码轻松爬取全部A股公司年报》
《实战演练-爬取深交所年报》
《【爬虫】用Python爬取公司年报》
《Python爬取上交所年报并下载》
后三个太麻烦了,需要分析网页,爬取链接,第一个最简单,因为他直接给出了所有公司年报的下载链接(好人一生平安),秉承着能复制粘贴绝不自己写的原则,选了第一个。但是这几个爬虫都有两个问题:
1.如果你爬着爬着中断了,需要重新爬取
2.都是单线程,尤其是这种网络IO,效率低
因此,本文对第一个爬虫使用了多线程进行了改写,还使用了xlwings记录实时下载状态。
爬虫思路
先看一下第一个爬虫给出的excel文件:
其实我们只需要前六列,因此我们将其复制粘贴到Excel中,再加入第七列标题state,以后用来记录下载状态。
注意:电脑上必须要有微软的office,否则后面爬虫没法用
简单来讲,我们的爬虫分为这几部分:
1.读取excel数据,获取下载链接
2.发起请求,接收pdf数据
3.把pdf进行存储
我们可以简化一下:
1.读取excel数据,获取下载链接
2.发起请求,接收pdf数据,把pdf进行存储
所以单线程爬虫总共分两步,但是每个文件下载都要等十几秒,效率非常低。
本文使用多线程的思路,多线程就是一心多用,如果你需要扫地,还需要蒸米饭,那你可以先把电饭煲打开,然后去扫地。当你做的每件事情都要等待,一心多用就会大大提高效率。
本文的思路是,把每个下载当成一个任务,先找个“任务管理员”,我们叫他Queue(),把所有下载任务全都告诉他,然后我们一心八用(开八门开八个线程),找Queue()要任务,执行任务,再去要任务,直到Queue()的任务全都派出去执行完了。代码如下:
先把需要的库导入,不需要问为什么
import xlwings as xw
import requests
from queue import Queue
import threading
import os
import time
进行一些初始化操作
# 把我们的excel路径写下来
file_path = r'E:\python\firmreport_spider2.0\公司竞争战略指标_2001_2019.xlsx'
# 初始化,打开excel,统计行列数
wb = xw.Book(file_path)
sht = wb.sheets['公司竞争战略指标_2001_2019']
#等待一会,打开太慢了
time.sleep(6)
# 获取行列数
info = sht.used_range
rows = info.last_cell.row
columns = info.last_cell.column
#创建一个任务管理员Queue()
q = Queue()
#设置八个线程
num_threads = 8
读取数据,提交给任务管理员q
# 把所有未下载链接提交给任务管理员q,等线程接取任务
def put_queue():
#把excel的所有数据给lists
lists = sht.range('A1').expand('table').value
#从第二行开始,逐行读取,直到最后一行
for i in range(1, rows):
#如果第i行的第7列(state那列)是空的,也就是没下载,把那行的数据提交给q
if not lists[i][6]:
code = str(int(lists[i][0]))
firm = lists[i][1].replace("*", "")
url = lists[i][4]
year = lists[i][2].year
n = i
q.put([code, firm, year, url, n])
print(code, firm, year, url, n)
定义存储路径和文件名称
# 这个函数只要拿到code,firm,year就能返回存储路径+文件名
def get_filepath(code, firm, year):
file_path = 'E:\\python\\firmreport_spider2.0\\下载年报'
file_name = "{}-{}-{}年年度报告.pdf".format(code, firm, year)
file_full_name = os.path.join(file_path, file_name)
return file_full_name
发送请求,进行下载,然后存储
# 拿到下载链接和存储路径,就会自动下载并存储
def download_pdf(url, file_full_name):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60'}
res = requests.get(url, headers=headers)
with open(file_full_name, "wb") as fp:
for chunk in res.iter_content(chunk_size=1024):
if chunk:
fp.write(chunk)
定义一个线程需要做的事,取任务,设置下载路径并制定文件名,下载存储,队列任务减一
#利用前面的函数定义一个线程需要做的事,取任务,设置下载路径并制定文件名,下载存储,队列任务减一
def single():
while True:
#找q要任务信息
info = q.get()
#根据任务信息先把文件全名和路径写好
file_full_name = get_filepath(info[0], info[1], info[2])
#把链接和路径给下载器,自动下载和存储
download_pdf(info[3], file_full_name)
#下载成功后在excel中记录为YES
xw.Range((info[4]+1, 7)).value = 'YES'
#q里的任务数减一
q.task_done()
最后写一个总的流程
if __name__ == '__main__':
# 把我们的excel路径写下来
file_path = r'E:\python\firmreport_spider2.0\公司竞争战略指标_2001_2019.xlsx'
# 初始化,打开excel,统计行列数
wb = xw.Book(file_path)
sht = wb.sheets['公司竞争战略指标_2001_2019']
#等待一会,打开太慢了
time.sleep(6)
# 获取行列数
info = sht.used_range
rows = info.last_cell.row
columns = info.last_cell.column
#创建一个任务管理员Queue()
q = Queue()
#设置八个线程
num_threads = 8
#开启八门
for i in range(num_threads):
t = threading.Thread(target=single)
t.daemon = True
t.start()
# 启动任务管理员
put_queue()
#等待q里的任务没了结束
q.join()
所有的代码如下:
import xlwings as xw
import requests
from queue import Queue
import threading
import os
import time
# 把所有未下载链接放入队列,放入队列就变成了任务,等线程接取任务
def put_queue():
lists = sht.range('A1').expand('table').value
for i in range(1, rows):
if not lists[i][6]:
code = str(int(lists[i][0]))
firm = lists[i][1].replace("*", "")
url = lists[i][4]
year = lists[i][2].year
n = i
q.put([code, firm, year, url, n])
print(code, firm, year, url, n)
# 定义存储路径和文件名称
def get_filepath(code, firm, year):
file_path = 'E:\\python\\firmreport_spider2.0\\下载年报'
file_name = "{}-{}-{}年年度报告.pdf".format(code, firm, year)
file_full_name = os.path.join(file_path, file_name)
return file_full_name
# 发送请求,进行下载,然后存储
def download_pdf(url, file_full_name):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60'}
res = requests.get(url, headers=headers)
with open(file_full_name, "wb") as fp:
for chunk in res.iter_content(chunk_size=1024):
if chunk:
fp.write(chunk)
#定义一个线程需要做的事,取任务,设置下载路径并制定文件名,下载存储,队列任务减一
def single():
while True:
info = q.get()
file_full_name = get_filepath(info[0], info[1], info[2])
download_pdf(info[3], file_full_name)
#下载成功后记录为YES
xw.Range((info[4]+1, 7)).value = 'YES'
q.task_done()
if __name__ == '__main__':
# 初始化,打开excel,统计行列数
file_path = r'E:\python\firmreport_spider2.0\公司竞争战略指标_2001_2019.xlsx'
wb = xw.Book(file_path)
sht = wb.sheets['公司竞争战略指标_2001_2019']
time.sleep(3)
# 获取行列数
info = sht.used_range
rows = info.last_cell.row
columns = info.last_cell.column
# print(rows,columns)
q = Queue()
num_threads = 8
for i in range(num_threads):
t = threading.Thread(target=single)
t.daemon = True
t.start()
# 启动队列
put_queue()
q.join()