【问题标题】:Python beautifulsoup parsing speed improvementPython beautifulsoup 解析速度提升
【发布时间】:2017-04-24 04:29:22
【问题描述】:

目前我已经编写了我的第一个 python 脚本,以便遍历 CSV 中列出的一些 URL。超过 14,000 个链接。我正在尝试 1)获取所有关键字标签 2)检查页面状态(需要标记 404 个链接)。 3) 将 youtube 视频转换为嵌入的 youtube 链接(可能在进入网页获取关键字然后转换为嵌入链接之后)

速度太慢了,但我想不出更快的方法。我觉得它是 request.get() 属性,但我不知道如何加快速度。我只需要元数据,但他们是一种只获取页面开头而不是全部的方法吗?我如何使这段代码更好/更快/优化。

另外,在使用 pyinstaller 进行编译时,我收到了一个集合问题。我觉得我在 python 3 中使用 python 2 代码.. 因为我正在使用 python 3.5 编写

import requests
from bs4 import BeautifulSoup
import csv
import re
import time

linkLocation = r'C:\Users\JCModern\Desktop\content_links.csv'
source_code = ''
myURL = ''
timestr = time.strftime("%Y%m%d_%H%M%S")
newfilename = r'C:\Users\JCModern\Desktop\content_links_and_keywords_' + timestr + '.csv'

with open(newfilename, "w", newline='') as file:
    writer = csv.writer(file, delimiter=',')
    writer.writerow(('cmsid', 'filepath', 'metatags', 'pageurl', 'pageurlchange'))
file.close()
with open(linkLocation, "r", encoding="utf-8-sig") as f:
    csv_f = csv.reader(f, delimiter=",")
    next(csv_f, None)
    for row in csv_f:
        if len(row) != 0:
            # init variables
            myKeywords = ""
            myTitle = ''
            myURL = ''
            pageUrlChange = ''
            pageStatus = ''
            pageUrl = ''
            myCmsid = (row[0])
            myURL = (row[2])
            if "https://www.youtube.com/embed/" in myURL:
                youtubeurl = myURL.split('/')
                youtubeurl = youtubeurl[4]
                youtubeurl = re.sub(
                    r'\?|\&|\=|re(l=\d+|l)|featur(e=sub|e)|playnext|video(s=\w+|s)|watch|_?((youtu(\.be|be))|fro(m=TL|m)|gdata|player|lis(t=\w+|t)|(inde(x=\w+|x)))_?|(v|vi)=|channel|ytscreeningroom','', youtubeurl)
                myURL = 'https://www.youtube.com/watch?v=' + youtubeurl
            try:    
                source_code = requests.get(myURL)
            except Exception:
                with open('errors.txt', 'a', newline='') as file:
                    writer = csv.writer(file, delimiter=',')
                    writer.writerow((myCmsid, myURL))
                file.close()
            pageStatus = source_code.status_code
            plain_text = source_code.text
            soup = BeautifulSoup(plain_text, 'html.parser')
            pageStatus = str(pageStatus)
            pageStatus = pageStatus[:1]
            pageStatus = int(pageStatus)
            if pageStatus == 2:
                pageUrlChange = 0
            else:
                pageUrlChange = 1
            if pageStatus == 3:
                pageUrl = source_code.url
            l = soup.findAll("meta", attrs={"name": "keywords"})
            if l is None:
                myKeywords = ""
            else:
                try:
                    myKeywords = l[0]['content']
                except:
                    myKeywords = myKeywords
                myKeywords = myKeywords.replace(', ', '~')
                myKeywords = myKeywords.replace(',', '~')
                myKeywords = myKeywords.replace('(', '')
                myKeywords = myKeywords.replace(')', '')
            if soup.find('title'):
                myTitle = soup.find('title').string
            if "https://www.youtube.com/" in myURL:
                youtubeurl = myURL.split('/')
                youtubeurl = youtubeurl[3]
                youtubeurl = re.sub(r'\?|\&|\=|re(l=\d+|l)|featur(e=sub|e)|playnext|video(s=\w+|s)|watch|_?((youtu(\.be|be))|fro(m=TL|m)|gdata|player|lis(t=\w+|t)|(inde(x=\w+|x)))_?|(v|vi)=|channel|ytscreeningroom','', youtubeurl)
                myURL = 'https://www.youtube.com/embed/' + youtubeurl
#                print(youtubeurl)
            if "https://youtu.be/" in myURL:
                youtubeurl = myURL.split('/')
                youtubeurl = youtubeurl[3]
                youtubeurl = re.sub(
                    r'\?|\&|\=|re(l=\d+|l)|featur(e=sub|e)|playnext|video(s=\w+|s)|watch|_?((youtu(\.be|be))|fro(m=TL|m)|gdata|player|lis(t=\w+|t)|(inde(x=\w+|x)))_?|(v|vi)=|channel|ytscreeningroom','', youtubeurl)
                myURL = 'https://www.youtube.com/embed/' + youtubeurl
#                print(youtubeurl)
#            print((myCmsid, myURL, myKeywords, pageUrl, pageUrlChange))
            with open(newfilename, "a", newline='') as file:
                writer = csv.writer(file, delimiter=',')
                writer.writerow((myCmsid, myURL, myKeywords, pageUrl, pageUrlChange))
            file.close()
f.close()

【问题讨论】:

  • 下面有两个有价值的答案,我正准备写这两个答案。 lxml 和多处理(如果您有 2 个以上的内核)。还可以考虑 linecache 模块。

标签: python performance python-3.x beautifulsoup


【解决方案1】:

除了迁移到更快的 xml 解析器的极好建议之外,这是通过 multiprocessing 模块进行并行化的理想选择。我已经重新安排了您的代码,以便在可以委托给子进程的工作人员中执行请求/解析。工作人员返回需要添加到 csv 的行。我在返回行的前面添加了一个 0/​​-1 错误代码,以便父进程知道哪个 csv 得到了结果。

import requests
from bs4 import BeautifulSoup
import csv
import re
import time
import multiprocessing
import traceback

def grabber(myCmsid, myURL):
    try:
        return grabber_impl(myCmsid, myURL)
    except:
        return (-1, myCmsid, myURL, traceback.format_exc())

def grabber_impl(myCmsid, myURL):
    # init variables
    myKeywords = ""
    myTitle = ''
    myURL = ''
    pageUrlChange = ''
    pageStatus = ''
    pageUrl = ''
    if "https://www.youtube.com/embed/" in myURL:
        youtubeurl = myURL.split('/')
        youtubeurl = youtubeurl[4]
        youtubeurl = re.sub(
            r'\?|\&|\=|re(l=\d+|l)|featur(e=sub|e)|playnext|video(s=\w+|s)|watch|_?((youtu(\.be|be))|fro(m=TL|m)|gdata|player|lis(t=\w+|t)|(inde(x=\w+|x)))_?|(v|vi)=|channel|ytscreeningroom','', youtubeurl)
        myURL = 'https://www.youtube.com/watch?v=' + youtubeurl

    source_code = requests.get(myURL)
    pageStatus = source_code.status_code
    plain_text = source_code.text
    soup = BeautifulSoup(plain_text, 'html.parser')
    pageStatus = str(pageStatus)
    pageStatus = pageStatus[:1]
    pageStatus = int(pageStatus)
    if pageStatus == 2:
        pageUrlChange = 0
    else:
        pageUrlChange = 1
    if pageStatus == 3:
        pageUrl = source_code.url
    l = soup.findAll("meta", attrs={"name": "keywords"})
    if l is None:
        myKeywords = ""
    else:
        try:
            myKeywords = l[0]['content']
        except:
            myKeywords = myKeywords
        myKeywords = myKeywords.replace(', ', '~')
        myKeywords = myKeywords.replace(',', '~')
        myKeywords = myKeywords.replace('(', '')
        myKeywords = myKeywords.replace(')', '')
    if soup.find('title'):
        myTitle = soup.find('title').string
    if "https://www.youtube.com/" in myURL:
        youtubeurl = myURL.split('/')
        youtubeurl = youtubeurl[3]
        youtubeurl = re.sub(r'\?|\&|\=|re(l=\d+|l)|featur(e=sub|e)|playnext|video(s=\w+|s)|watch|_?((youtu(\.be|be))|fro(m=TL|m)|gdata|player|lis(t=\w+|t)|(inde(x=\w+|x)))_?|(v|vi)=|channel|ytscreeningroom','', youtubeurl)
        myURL = 'https://www.youtube.com/embed/' + youtubeurl
#                print(youtubeurl)
    if "https://youtu.be/" in myURL:
        youtubeurl = myURL.split('/')
        youtubeurl = youtubeurl[3]
        youtubeurl = re.sub(
            r'\?|\&|\=|re(l=\d+|l)|featur(e=sub|e)|playnext|video(s=\w+|s)|watch|_?((youtu(\.be|be))|fro(m=TL|m)|gdata|player|lis(t=\w+|t)|(inde(x=\w+|x)))_?|(v|vi)=|channel|ytscreeningroom','', youtubeurl)
        myURL = 'https://www.youtube.com/embed/' + youtubeurl
#                print(youtubeurl)
#            print((myCmsid, myURL, myKeywords, pageUrl, pageUrlChange))
    return (0, myCmsid, myURL, myKeywords, pageUrl, pageUrlChange))


linkLocation = r'C:\Users\JCModern\Desktop\content_links.csv'
source_code = ''
myURL = ''
timestr = time.strftime("%Y%m%d_%H%M%S")
newfilename = r'C:\Users\JCModern\Desktop\content_links_and_keywords_' + timestr + '.csv'

with open(linkLocation, "r", encoding="utf-8-sig") as f:
    csv_f = csv.reader(f, delimiter=",")
    next(csv_f, None)
    pool = multiprocessing.Pool()

    with open(newfilename, 'a', newline='') as out, open('errors.txt', 'a', newline='') as err:
        writer = csv.writer(out, delimiter=',')
        err_writer = csv.writer(err, delimiter=',')
        for result in pool.imap_unordered(grabber, ((row[0], row[2]) for row in csv_f if row), chunksize=1):
            if result[0]:
                writer.writerow(result[1:])
            else:
                print(result[3])
                err_writer.writerow(result[1:3])
pool.close()
pool.join()

【讨论】:

  • 感谢您的回复,但这段代码似乎并没有真正做任何事情。我已经让它运行了 15 分钟,现在创建了 error.txt 文件,但其中没有任何内容,并且创建了 content_links_and_keywords_date.csv 文件,但其中没有任何内容。我取消注释“ print((myCmsid, myURL, myKeywords, pageUrl, pageUrlChange)) ”并且从不返回任何内容
  • 我更新了示例,让异常处理程序覆盖整个工作人员。 Pool.imap_unordered 将重新引发父级的执行,也许那是你的问题。我看到您试图更改代码以获得更好的 html 解析器,......好主意!但也许在这个问题上做得最好......这个变化只是一个例子 - 因为我无法运行它,所以我无法测试它。一种选择是我们都可以运行的更短的示例。
【解决方案2】:

html.parser 是使用正则表达式的纯 python 实现。你真的不想使用它。安装 lxml 并改为使用 C 代码进行解析(记得使用 BeautifulSoup(plain_text, 'lxml')

您也不想一直重新打开 CSV 文件。在循环外打开它一次,然后将新行写入循环中的csv.writer() 对象。

否则,您无法加快 URL 加载速度。网络速度永远是瓶颈。您可以使用 very 低级别的PyCurl library,但我怀疑它可以提供的加速效果在这里会产生影响。

【讨论】:

  • 迟到了,但是异步请求呢,使用 aiohttp 作为 url 查询部分?这不应该可以方便地加快查询链接的数量吗?
  • @LucBertin asyncio 无助于使请求-响应周期更快,当您通过让计算机执行 other 向许多不同的 URL 发出请求时,它会有所帮助在等待时工作。如果您需要处理大量 URL,您可能会在等待其他请求的数据到达时解析已完成请求的响应。
  • 是的@Martijn,对不起,我并不是要加快请求-响应周期,而是实际上让计算机在发送“大量”请求的同时执行其他任务。 “在等待数据的同时工作的空闲时间”难道不是在路的尽头大大加快速度吗?
  • @LucBertin:绝对!对于这类任务,我更喜欢使用 asyncio,这样可以显着提高速度。
猜你喜欢
  • 2013-04-24
  • 1970-01-01
  • 1970-01-01
  • 2012-01-26
  • 1970-01-01
  • 1970-01-01
  • 2020-02-06
  • 2011-05-03
  • 2014-03-06
相关资源
最近更新 更多