前段日子学了网络爬虫,却没有实际投入使用。于是我就想找一个目标。正好在论坛上看到许多关于爬取网上视频的文章。这不机会就来了?于是,我就以爬取神探狄仁杰这个视频为例来讲一下爬虫过程中的心得与体会吧。
首先展示一下代码吧。代码如下:
分析数据部分:
#test.py
class video_msg:
def __init__(self, file_url, no, path):
self.__no__ = no
self.__file_url__ = file_url
self.__pa__ = path
def get_no(self):
return self.__no__
def get_file_url(self):
return self.__file_url__
def get_path(self):
return self.__pa__
def files_objective(path):
import re
file_lists = []
url_msg = open('urls.txt', 'r')
urls = url_msg.read()
url_msg.close()
base_url = re.findall(r"https[A-Za-z0-9/:.]{0,80}", urls)
for i in range(1,len(base_url)+1,1):
file_lists.append(video_msg(file_url=base_url[i-1],no="%04d"%i,path=path))
return file_lists
爬取数据部分:
#test.py
import multiprocessing
import threading
import time
import requests
import requests_cache
import urllib3
import os
headers = {'Accept': '*/*',
'Accept-Encoding': 'identity',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Origin': 'https://www.xxxxx.com/',
'Referer': 'https://www.xxxxx.com/',
'Sec-Ch-Ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Microsoft Edge";v="114"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.43'}
class down_thread(threading.Thread):
def __init__(self,no,path,url):
super().__init__()
self.no=no
self.pa=path
self.url=url
def run(self):
super().run()
proxy={'http':'124.89.187.33'}
if not f"{self.no}.ts" in os.listdir():
response=requests.get(url=self.url,verify=False,proxies=proxy,headers=headers)
if response.status_code==404:
os.system(f"echo 错误文件:{self.pa}.ts,地址:{self.url}>>error.txt")
print(f"当前文件:{self.no}.ts,地址:{self.url}",end='\n')
with open(f"{self.no}.ts",'wb+')as fe:
fe.write(response.content)
fe.close()
class define_process(multiprocessing.Process):
def __init__(self,begin,end):
super().__init__()
self.begin=begin
self.end=end
def run(self):
super().run()
test = files_objective(path=1)
threads = []
for i in range(self.begin,self.end,1):
threads.append(down_thread(no=test[i].get_no(), path=test[i].get_path(), url=test[i].get_file_url()))
for sub_thread in threads:
sub_thread.start()
sub_thread.join()
class check_process(multiprocessing.Process):
def __init__(self):
super().__init__()
def run(self) -> None:
super().run()
test=files_objective(path=1)
threads=[]
for t in test:
threads.append(down_thread(no=t.get_no(),path=t.get_path(),url=t.get_file_url()))
for sub_thread in threads:
sub_thread.start()
sub_thread.join()
if __name__ == '__main__':
requests_cache.clear()
requests_cache.install_cache()
print(len(files_objective(path=1)))
processes=[check_process() for i in range(0,12,1)]
for sub in processes:
sub.start()
'''
processes=[define_process(begin=i*int(len(files_objective(path=1))/12),end=(i+1)*int(len(files_objective(path=1))/12)) for i in range(0,12,1)]
for sub in processes:
sub.start()
'''
数据处理部分(文件归类保存):
#move_dir.py
import os
if __name__ == '__main__':
os.mkdir(rf"C:\Users\12952\Desktop\神探狄仁杰\{30}") #30这个数字可变,取决于集数
os.system(rf"move *.ts C:\Users\12952\Desktop\神探狄仁杰\{30}")
数据处理部分(合并部分):
#comband.py
import os
import time
def once(nums):
for i in range(0,int(nums/10)+1,1):
os.system(r"copy /b %03d?.ts %03d.ts"%(i,i))
def twice(nums):
for i in range(0,int(nums/100)+1,1):
os.system(r"copy /b %02d?.ts %02d.ts"%(i,i))
def third(nums):
for i in range(0,int(nums/1000)+1,1):
os.system(r"copy /b %01d?.ts %01d.ts"%(i,i))
def final(no):
os.system(r"copy /b ?.ts %s.ts"%no)
os.system(r"move %s.ts C:\Users\12952\Desktop\fetch"%no)
time.sleep(3)
os.system(r"del *.ts")
if __name__=='__main__':
name=input("请输入名字:")
nums=len(os.listdir())-1
once(nums)
twice(nums)
third(nums)
final(str(name))
os.system("pause")
其次讲一下分析数据部分的工作原理吧。大部分的视频网站加载视频都会以文件后缀名为m3u8的文本内容来动态加载ts格式的碎片视频。这些碎片视频每个都只有一两秒钟时间。所以遇见以序号为名字的ts文件好归类合并。但是笔者在看到m3u8文件里的内容时不淡定了。因为全是英文加数字开头的ts文件。以下就是其中之一:
看起来是不是眼花缭乱的?但是我有办法。我可以先顺序读取这个文本文件的内容。然后用正则表达式来匹配文本中的视频地址。已知re.findall(pattern,str)方法会返回一个列表类型的数据。所以我再写一个文件信息类来加工正则表达式匹配到的数据。文件信息类代码如下:
class video_msg:
def __init__(self, file_url, no, path):
self.__no__ = no
self.__file_url__ = file_url
self.__pa__ = path
def get_no(self):
return self.__no__
def get_file_url(self):
return self.__file_url__
def get_path(self):
return self.__pa__
通过文件信息类可以顺序地得到一个ts视频文件说对应的url地址、文件编号、父目录名。以便接下来的爬取数据过程。然后通过for循环将视频文件对应的文件信息类装入到列表里并在最后返回列表类型的数据。代码如下:
def files_objective(path):
import re
file_lists = []
url_msg = open('urls.txt', 'r')
urls = url_msg.read()
url_msg.close()
base_url = re.findall(r"https[A-Za-z0-9/:.]{0,80}", urls)
for i in range(1,len(base_url)+1,1):
file_lists.append(video_msg(file_url=base_url[i-1],no="%04d"%i,path=path))
return file_lists
讲完了数据分析部分,该讲讲数据爬取部分了。此次爬虫使用的python库有requests、requests_cache、os、multiprocessing、threading。对应的技术是利用多进程与多线程协调使用快速高效地爬取视频、伪造请求头、缓存机制、代理ip等等。大致思路就是把一个视频所有的ts文件对应的文件信息类列表分段成一个个小的列表。这每个列表可供不同的进程进行爬取写入文件等操作。每个进程又通过范围循环把这些小的列表包装成线程爬取类。线程爬取类代码如下:
class down_thread(threading.Thread):
def __init__(self,no,path,url):
super().__init__()
self.no=no
self.pa=path
self.url=url
def run(self):
super().run()
proxy={'http':'124.89.187.33'}
if not f"{self.no}.ts" in os.listdir():
response=requests.get(url=self.url,verify=False,proxies=proxy,headers=headers)
if response.status_code==404:
os.system(f"echo 错误文件:{self.pa}.ts,地址:{self.url}>>error.txt")
print(f"当前文件:{self.no}.ts,地址:{self.url}",end='\n')
with open(f"{self.no}.ts",'wb+')as fe:
fe.write(response.content)
fe.close()
由于这里使用了分段爬取的策略,爬取速度大幅度提高。我分配了12个进程。所以爬的时候大致就是一秒一二十个ts文件下载速度。但是缺点也很明显:ts文件的数量与m3u8文件的文件信息数对不上以至于漏爬。于是我又设立了检查进程,也就是检查策略。检查进程与爬取进程不一样,前者最后使用以检查文件缺陷,后者是将一个视频的数据分成多个进程爬取以提高效率。爬取进程的代码如下:
class define_process(multiprocessing.Process):
def __init__(self,begin,end):
super().__init__()
self.begin=begin
self.end=end
def run(self):
super().run()
test = files_objective(path=1)
threads = []
for i in range(self.begin,self.end,1):
threads.append(down_thread(no=test[i].get_no(), path=test[i].get_path(), url=test[i].get_file_url()))
for sub_thread in threads:
sub_thread.start()
sub_thread.join()
检查进程对应的代码如下:
class check_process(multiprocessing.Process):
def __init__(self):
super().__init__()
def run(self) -> None:
super().run()
test=files_objective(path=1)
threads=[]
for t in test:
threads.append(down_thread(no=t.get_no(),path=t.get_path(),url=t.get_file_url()))
for sub_thread in threads:
sub_thread.start()
sub_thread.join()
然后讲一下数据处理中的文件归类部分吧。文件归类部分使用了windows系统自带的批处理命令。先使用os.mkdir(path)方法创建一个视频对应的集数文件夹。再使用os.system(command)方法调用move指令移动所有的ts文件。文件归类部分代码如下:
import os
if __name__ == '__main__':
os.mkdir(rf"C:\Users\12952\Desktop\神探狄仁杰\{30}")#数字30可变
os.system(rf"move *.ts C:\Users\12952\Desktop\神探狄仁杰\{30}")
最后讲一下数据处理中的合并部分。这里我使用了move、copy、del等指令。已知copy /b source destination指令合并ts文件的能力有限,但我可以使用循环语句将多个视频合并成多个小段,然后再把多个小段慢慢合并成一个完整的视频文件。最后命名并移动文件到指定目录即可。合并部分代码如下:
import os
import time
def once(nums):
for i in range(0,int(nums/10)+1,1):
os.system(r"copy /b %03d?.ts %03d.ts"%(i,i))
def twice(nums):
for i in range(0,int(nums/100)+1,1):
os.system(r"copy /b %02d?.ts %02d.ts"%(i,i))
def third(nums):
for i in range(0,int(nums/1000)+1,1):
os.system(r"copy /b %01d?.ts %01d.ts"%(i,i))
def final(no):
os.system(r"copy /b ?.ts %s.ts"%no)
os.system(r"move %s.ts C:\Users\12952\Desktop\fetch"%no)
time.sleep(3)
os.system(r"del *.ts")
if __name__=='__main__':
name=input("请输入名字:")
nums=len(os.listdir())-1
once(nums)
twice(nums)
third(nums)
final(str(name))
os.system("pause")
至此,我的爬虫心得与体会讲到这里就结束了。爬取的效果如下:
感觉还行。就是如果这些部分可以自动化执行就好了。当然我可以日后慢慢改进的。最后,感谢大家的耐心观看。