声明:如果有什么错误,还请多多指正,或者有更好的方法,也请多指教!
1. 环境配置
VS Code配置使用 Python,超详细配置指南,看这一篇就够了_vscode配置python开发环境-CSDN博客
MacOs,使用vscode,需要先配置python环境
在vscode的extensions(扩展)中安装python插件,然后创建虚拟环境。
Python 开发者的最佳实践是使用特定于项目的 virtual environment 。一旦激活该环境,安装的任何软件包都将与其他环境(包括全局解释器环境)隔离,从而减少因软件包版本冲突而可能引起的许多复杂情况。
可以使用 Venv 或 Conda 和 Python 在 VS Code 中创建非全局环境:创建环境。
打开命令面板 ( ⇧⌘P ),开始键入 Python: Create Environment 命令进行搜索,然后选择该命令。该命令显示环境类型列表,Venv 或 Conda。
以Venv为例,虚拟环境创建好之后会出现在工作区中,之后创建源文件,开始写代码!
2. 背景知识
2.1 爬虫原理
按照我的理解,浅显的说,爬虫就是模拟一个客户端,与服务器产生连接,自动向服务器请求相关服务,也就是自己所需要的页面,服务器会返回一个响应,该响应就是想要爬取的页面,通过分析该页面结构,获取自己想要的信息。而不需要手动点击访问页面。
简单的爬虫,适用于本爬取程序,需要了解http请求,网页结构(html格式)。
2.2 http和html
http是一种传输协议hyper text transfer protocol,按照该协议进行服务的请求与响应,常见的请求方式有GET,POST,常见的响应有202(成功),404(not found)等。
需要知道想要爬取的页面的地址,即URL(uniform resource locator),才能建立连接,以及知道请求该页面的方式是GET或者POST,以及请求的header和payload,这些信息可以通过在页面右键,点击“检查”来查看当前页面的源码及XHR(XML Http Request)。另外需要注意页面又分为静态页面和动态页面,静态页面指的是对于某个URL,页面内容保持不变,而动态页面则是URL不变,但是通过AJAX来更新页面内容,比如我在当前页面上点击一个按钮,会显示出新的内容,但是页面URL却保持不变。
HTML 是hyper text markup language,超文本标记语言。页面都是用html+css来写成的,css主要负责页面的样式,html中会包含页面的内容。所以比较重要的是需要知道html的语法,每个代码块之间的分割,各个标签的区分,也要掌握一些html的分析工具(在这里我使用的是beautifulsoup,能够检索不同的标签或者选择器),来提取需要的内容。
3.解决方案分析
参考:不想复制粘贴了,爬取TED视频的语言脚本_爬虫 ted-CSDN博客
为什么想要通过爬虫来获取transcript呢,因为我在找各个transcript的时候觉得有些麻烦,要手动一个个去copy,所以想到用爬虫这个自动的方法。诚然,学习写爬虫的过程也花费不少时间,也许有这个时间早就copy完了,但之后就可以省些力气了,并且可以沿用到其他爬虫项目中。总之,收获了许多爬虫的知识,也实现了想要的结果。
3.1入口页面html分析
入口页面url:TED: Ideas Worth Spreading
该页面只显示最新的24个视频,点击“view more”才会显示更多的页面,但是页面URL不会变,是一种动态页面。
首先看一下该请求的方法, header以及payload。我们可以打开谷歌浏览器,然后输入入口页面的URL,然后右键,点击“检查”
然后刷新点击“network”,再点击“Fetch/XHR”,然后刷新该页面(重新请求),在左侧找到search
点击后如下图所示,有header,还有payload等等,观察发现,在点击“view more”之后,除了payload中的“page”每次不同,从0到1到2....,其他参数是一样的,可以知道页面动态页面的内容由该参数配置决定。
请求该页面得到的html响应中,可以找到该页面上的24个视频的链接,如下图所示:每个canonicalUrl后面的就是一个视频的链接,组成的结构是:https://www.ted.com/talks/<speaker_name>_<speech_name>
那么接下来要知道的就是如何将这一部分从整个html中区分出来,可以看看有没有该标签独一无二的名字等,通过寻找,发现该标签的id选择器名字"__NEXT_DATA__"是独一无二的,可以用作提取的标志。提取使用beautifulsoup。
3.2 获取trancript
在获取每个视频的单独链接之后,后面加上transcript.json?language=en的新URL则是transcript所在的页面,语言是英语,如果需要其他语言,可以在language处更换。新url例子如下所示:
https://www.ted.com/talks/katie_fahey_a_crash_course_in_making_political_change/transcript.json?language=en
另外一个方法是,有transcript的页面下面会有‘read transcript’的按钮,点击并选择语言后会在右侧出现相应文稿并且当前url变为视频链接+'/transcript/'。所以可以请求相应的transcript页面,得到相应后,分析transcript存在于哪个标签,并将其区分出来。
探索发现,可以通过标签的type='application/ld+json'将transcript的标签区分出来,就得到文稿了。
4. 实现代码
# encoding: utf-8
# @time : 2021-08-26 12:28:00
# @file : ted_spider.py
# @software : {
# @author : m0_46156900
# blog : https://blog.csdn.net/m0_46156900
# 导入库
import json
import os
import time
import requests
from bs4 import BeautifulSoup
import logging as log
import html
from MD_TEMPlATE import md_template
import textwrap
import docx
from docx.shared import Pt
class tedTalk:
header = {
'Referer': 'https://www.ted.com/',
'User-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
'Content-type': 'application/json'
}
payload = {
'indexName': 'newest',
'params':{
'attributeForDistinct': 'objectID',
'distinct': 1,
'facets':['subtitle_languages', 'tags'],
'highlightPostTag': '__/ais-highlight__',
'highlightPreTag': '__ais-highlight__',
'hitsPerPage': 24,
'maxValuesPerFacet': 500,
'page': 2,
'query': '',
'tagFilters': ''
}
}
payloadJson = json.dumps(payload)
languages = {
'English': 'en',
'Chinese_simplified': 'zh-cn',
}
def __init__(self, entry_url):
log.basicConfig(filename='log.txt', level=log.INFO)
if not os.path.isdir('/Users/zhang/repo/pachongTED/Transcript/TranscriptJsonFile'):
os.mkdir(path='/Users/zhang/repo/pachongTED/Transcript/TranscriptJsonFile')
if not os.path.isdir('/Users/zhang/repo/pachongTED/Transcript/MarkdownFile'):
os.mkdir(path='/Users/zhang/repo/pachongTED/Transcript/MarkdownFile')
if not os.path.isdir('/Users/zhang/repo/pachongTED/Transcript/textFile'):
os.mkdir(path='/Users/zhang/repo/pachongTED/Transcript/textFile')
if not os.path.isdir('/Users/zhang/repo/pachongTED/Transcript/docxFile'):
os.mkdir(path='/Users/zhang/repo/pachongTED/Transcript/docxFile')
self.title = None
self.JsonFile = None
self.originalUrl = entry_url
self.entry_url = entry_url + '/transcript.json?language=en'
self.entry_resp = self.request(url=self.entry_url)
self.talkLink = None
self.textFile = None
self.payloadJson = None
def changePage(self):
pageNum = input("please input the page number:")
self.payload['params']['page'] = int(pageNum)
log.info('Set page successfully!')
self.payloadJson = json.dumps(self.payload)
self.entry_resp = self.request(url=self.entry_url)
def request(self, url):
retry = 5
while retry:
log.info('进行第%d次访问:%s' % (6-retry,url))
try:
print(self.payloadJson)
resp = requests.get(url=url,headers=self.header,data = self.payloadJson, timeout=30) # 设置超时,这个TED网站可能需要多访问几次
resp = requests.get(url=url,headers=self.header, timeout=30)
resp.raise_for_status()
log.info('页面抓取成功!')
log.info(resp.text)
return resp.text
except Exception as e:
log.error("访问网页失败!,错误:\n %s" %(e))
retry -= 1
def requestTranscript(self, url):
retry = 5
while retry:
log.info('进行第%d次访问:%s' % (6-retry,url))
try:
resp = requests.get(url=url,headers=self.header,timeout=30) # 设置超时,这个TED网站可能需要多访问几次
resp.raise_for_status()
log.info('transcript页面抓取成功!')
return resp.text
except Exception as e:
log.error("访问transcript网页失败!,错误:\n %s" %(e))
retry -= 1
def getTalkLink(self):
soup = BeautifulSoup(self.entry_resp, 'html.parser')
dataTag = soup.select_one('#__NEXT_DATA__') #以id选择器名称过滤出需要的信息,每个page只存在一个该id
jsonData = dataTag.string
data = json.loads(jsonData)
if( 'talks' not in data['props']['pageProps'].keys()):
log.info('please try again!')
return None
talks = data['props']['pageProps']['talks']
links = []
for i in range(len(talks)):
links.append(talks[i]['canonicalUrl'] + '/transcript')
log.info(links)
return links
def getTranscript(self, links,language='en'): //通过json文件获取一页的所有视频的transcript
numTranscript = 1
for i in range(len(links)):
self.talkLink = links[i]
response = self.requestTranscript(url=self.talkLink)
soup = BeautifulSoup(response, 'html.parser')
json_url= self.talkLink + '.json?language=' + language #defalu language is english
# links[1]: https://www.ted.com/talks/katie_fahey_a_crash_course_in_making_political_change/transcript
self.JsonFile = self.talkLink.split('/')[4] + '.json'
self.title = self.talkLink.split('/')[4]
response = self.requestTranscript(url= json_url)
if response == None:
log.info("该视频没有transcript,请以后尝试!")
else:
numTranscript += 1
jsonPath = '/Users/zhang/repo/pachongTED/Transcript/TranscriptJsonFile/' + self.JsonFile
#print(jsonPath)
with open(jsonPath, 'w', encoding='utf-8') as f:
f.write(response)
f.close()
log.info('json文件保存成功!: %s' % self.JsonFile)
self.writeMarkDown()
printLog = '完成第{0}页所有视频transcript存储,共{1}个'.format(self.payload['params']['page'], numTranscript)
log.info(printLog)
def getTranscriptBySoup(self,links): //通过解析html的方法来获得transcript
numTranscript = 1
for i in range(len(links)):
numTranscript += 1
self.talkLink = links[i]
self.textFile = self.talkLink.split('/')[4] + '.text'
self.title = self.talkLink.split('/')[4]
response = self.requestTranscript(url=self.talkLink)
soup = BeautifulSoup(response, 'html.parser')
dataTag = soup.find('script', type='application/ld+json')
jsonData = json.loads(dataTag.string)
transcript_html = jsonData['transcript']
transcript = html.unescape(transcript_html)
wrapped_transcript = textwrap.fill(transcript, width=200)
with open('/Users/zhang/repo/pachongTED/Transcript/textFile/'+ self.textFile, 'w', encoding='utf-8') as f:
f.write(wrapped_transcript)
f.close()
printLog = '完成第{0}页所有视频transcript存储,共{1}个'.format(self.payload['page'], numTranscript)
log.info(printLog)
def writeMarkDown(self):
jsonPath = '/Users/zhang/repo/pachongTED/Transcript/TranscriptJsonFile/' + self.JsonFile
js_eng = json.loads(open(jsonPath).read())
engScript='### '.join(self.parseCuesTime(js_eng))
extListening = 'generated by python script'
md = md_template.format(self.title,self.talkLink,extListening,engScript)
mdFile = self.JsonFile.replace(".json",".md")
mdPath = '/Users/zhang/repo/pachongTED/Transcript/MarkdownFile/' + mdFile
with open(mdPath, 'w',encoding='utf-8') as f:
f.write(md)
f.close()
log.info('markDown文件保存成功!: %s' % mdFile)
@staticmethod
def parseCuesTime(jsonFile):
for parph in jsonFile['paragraphs']:
#{"cues":[{"time":246,"text":"This is the Ghazipur landfill\nin Delhi, India."},
# {"time":3374,"text":"It’s almost 20 stories high\nand it often collapses,"},
# {"time":6669,"text":"killing the waste pickers that work there."},
# {"time":9338,"text":"Last year, when temperatures\nhit 43 degrees Celsius,"},
# {"time":12842,"text":"it caught fire three times in a month\nand burned for 48 hours straight,"},
# {"time":16763,"text":"exposing the city\nto harmful particulate matter."}]}
parph_cues_sec = parph['cues'][-1]['time']/1000
parph_text = ''
for cues_text in parph['cues']:
parph_text+=cues_text['text'].replace('\n', ' ')+' '
parph_text+='\n'
item = time.strftime("%M:%S", time.localtime(parph_cues_sec))+'\n'+parph_text
yield item #item是一个生成器
def getTranscriptForSingleTalk(self): //一个视频的transcript
#entryUrl should be the link of the specific video
#example:https://www.ted.com/talks/katie_fahey_a_crash_course_in_making_political_change
print(self.entry_url)
self.JsonFile = self.originalUrl.split('/')[4] + '.json'
self.title = self.originalUrl.split('/')[4]
jsonPath = '/Users/zhang/repo/pachongTED/Transcript/TranscriptJsonFile/' + self.JsonFile
if self.entry_resp == None:
log.info("该视频没有transcript,请以后尝试!")
else:
#print(jsonPath)
with open(jsonPath, 'w', encoding='utf-8') as f:
f.write(self.entry_resp)
f.close()
log.info('json文件保存成功!: %s' % self.JsonFile)
self.writeDoc()
def main():
tTalk= tedTalk(entry_url= 'https://www.ted.com/talks/katie_fahey_a_crash_course_in_making_political_change')#
tTalk.getTranscriptForSingleTalk()
if __name__ == '__main__':
main()