python 爬虫获取TED talk文稿

声明:如果有什么错误,还请多多指正,或者有更好的方法,也请多指教!

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()

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值