【问题标题】:Speeding up beautifulsoup加速beautifulsoup
【发布时间】:2014-10-21 18:09:51
【问题描述】:

我正在运行这个课程网站的抓取工具,我想知道一旦我将页面放入 beautifulsoup,是否有更快的方法来抓取页面。花费的时间比我预期的要长。

提示?

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support import expected_conditions as EC

from bs4 import BeautifulSoup

driver = webdriver.PhantomJS()
driver.implicitly_wait(10) # seconds
driver.get("https://acadinfo.wustl.edu/Courselistings/Semester/Search.aspx")
select = Select(driver.find_element_by_name("ctl00$Body$ddlSchool"))

parsedClasses = {}

for i in range(len(select.options)):
    print i
    select = Select(driver.find_element_by_name("ctl00$Body$ddlSchool"))
    select.options[i].click()
    upperLevelClassButton = driver.find_element_by_id("Body_Level500")
    upperLevelClassButton.click()
    driver.find_element_by_name("ctl00$Body$ctl15").click()

    soup = BeautifulSoup(driver.page_source, "lxml")

    courses = soup.select(".CrsOpen")
    for course in courses:
        courseName = course.find_next(class_="ResultTable")["id"][13:]
        parsedClasses[courseName] = []
        print courseName
        for section in course.select(".SecOpen"):
            classInfo = section.find_all_next(class_="ItemRowCenter")
            parsedClasses[courseName].append((int(classInfo[0].string), int(classInfo[1].string), int(classInfo[2].string)))

print parsedClasses
print parsedClasses['FL2014' + 'A46' + '3284']

driver.quit()

【问题讨论】:

  • 如果您关心速度,请直接使用 lxml(使用 xpath 表达式)。
  • 这本质上是一个代码审查请求,因此不适合 SO。 SE 网络中有一个专门用于代码审查的站点:codereview.stackexchange.com
  • @Louis 我不同意,这也是关于从搜索结果中获取数据的替代方法。这不仅仅是关于代码及其质量。

标签: python selenium web-scraping html-parsing beautifulsoup


【解决方案1】:

好的,您可以通过以下方式加快速度:

由于这是ASP.NET 生成的表单,并且由于它的安全功能,事情变得有点复杂。这是完整的代码,不要害怕 - 我已经添加了 cmets 并有问题:

import re
from bs4 import BeautifulSoup, SoupStrainer
import requests

# start session and get the search page
session = requests.Session()
response = session.get('https://acadinfo.wustl.edu/Courselistings/Semester/Search.aspx')

# parse the search page using SoupStrainer and lxml
strainer = SoupStrainer('form', attrs={'id': 'form1'})
soup = BeautifulSoup(response.content, 'lxml', parse_only=strainer)

# get the view state, event target and validation values
viewstate = soup.find('input', id='__VIEWSTATE').get('value')
eventvalidation = soup.find('input', id='__EVENTVALIDATION').get('value')
search_button = soup.find('input', value='Search')
event_target = re.search(r"__doPostBack\('(.*?)'", search_button.get('onclick')).group(1)

# configure post request parameters
data = {
    '__EVENTTARGET': event_target,
    '__EVENTARGUMENT': '',
    '__LASTFOCUS': '',
    '__VIEWSTATE': viewstate,
    '__EVENTVALIDATION': eventvalidation,
    'ctl00$Body$ddlSemester': '201405',
    'ctl00$Body$ddlSession': '',
    'ctl00$Body$ddlDept': '%',
    'ctl00$Body$ddlAttributes': '0',
    'ctl00$Body$Days': 'rbAnyDay',
    'ctl00$Body$Time': 'rbAnyTime',
    'ctl00$Body$cbMorning': 'on',
    'ctl00$Body$cbAfternoon': 'on',
    'ctl00$Body$cbEvening': 'on',
    'ctl00$Body$tbStart': '9:00am',
    'ctl00$Body$tbEnds': '5:00pm',
    'ctl00$Body$ddlUnits': '0',
    'ctl00$Body$cbHideIStudy': 'on',
    'ctl00$Body$courseList$hidHoverShow': 'Y',
    'ctl00$Body$courseList$hidDeptBarCnt': '',
    'ctl00$Body$courseList$hidSiteURL': 'https://acadinfo.wustl.edu/Courselistings',
    'ctl00$Body$courseList$hidExpandDetail': '',
    'ctl00$Body$hidDay': ',1,2,3,4,5,6,7',
    'ctl00$Body$hidLevel': '1234',
    'ctl00$Body$hidDefLevel': ''
}

# get the list of options
strainer = SoupStrainer('div', attrs={'id': 'Body_courseList_tabSelect'})
options = soup.select('#Body_ddlSchool > option')
for option in options:
    print "Processing {option} ...".format(option=option.text)

    data['ctl00$Body$ddlSchool'] = option.get('value')

    # make the search post request for a particular option
    response = session.post('https://acadinfo.wustl.edu/Courselistings/Semester/Search.aspx',
                            data=data)
    result_soup = BeautifulSoup(response.content, parse_only=strainer)
    print [item.text[:20].replace('&nbsp', ' ') + '...' for item in result_soup.select('div.CrsOpen')]

打印:

Processing Architecture ...
[u'A46 ARCH 100...', u'A46 ARCH 111...', u'A46 ARCH 209...', u'A46 ARCH 211...', u'A46 ARCH 266...', u'A46 ARCH 305...', u'A46 ARCH 311...', u'A46 ARCH 323...', u'A46 ARCH 328...', u'A46 ARCH 336...', u'A46 ARCH 343...', u'A46 ARCH 350...', u'A46 ARCH 355...', u'A46 ARCH 411...', u'A46 ARCH 422...', u'A46 ARCH 428...', u'A46 ARCH 436...', u'A46 ARCH 445...', u'A46 ARCH 447...', u'A46 ARCH 465...', u'A48 LAND 451...', u'A48 LAND 453...', u'A48 LAND 461...']
Processing Art ...
[u'F10 ART 1052...', u'F10 ART 1073...', u'F10 ART 213A...', u'F10 ART 215A...', u'F10 ART 217B...', u'F10 ART 221A...', u'F10 ART 231I...', u'F10 ART 241D...', u'F10 ART 283T...', u'F10 ART 301A...', u'F10 ART 311E...', u'F10 ART 313D...', u'F10 ART 315B...', u'F10 ART 317H...', u'F10 ART 323A...', u'F10 ART 323B...', u'F10 ART 323C...', u'F10 ART 329C...', u'F10 ART 337E...', u'F10 ART 337F...', u'F10 ART 337H...', u'F10 ART 385A...', u'F10 ART 391M...', u'F10 ART 401A...', u'F10 ART 411E...', u'F10 ART 413D...', u'F10 ART 415B...', u'F10 ART 417H...', u'F10 ART 423A...', u'F10 ART 423B...', u'F10 ART 423C...', u'F10 ART 429C...', u'F10 ART 433C...', u'F10 ART 433D...', u'F10 ART 433E...', u'F10 ART 433K...', u'F10 ART 461C...', u'F10 ART 485A...', u'F20 ART 111P...', u'F20 ART 115P...', u'F20 ART 1186...', u'F20 ART 119C...', u'F20 ART 127A...', u'F20 ART 133B...', u'F20 ART 135G...', u'F20 ART 135I...', u'F20 ART 135J...', u'F20 ART 1361...', u'F20 ART 1363...', u'F20 ART 1713...', u'F20 ART 219C...', u'F20 ART 2363...', u'F20 ART 2661...', u'F20 ART 281S...', u'F20 ART 311P...', u'F20 ART 315P...', u'F20 ART 3183...', u'F20 ART 333B...', u'F20 ART 335A...', u'F20 ART 335J...', u'F20 ART 3713...', u'F20 ART 381S...', u'F20 ART 415P...', u'F20 ART 435I...']
...

这里肯定有一些需要改进的地方,例如,我已经硬编码了其他表单值 - 您可能应该解析可能的值并适当地设置它们。

另一个改进是将其绑定到grequests

GRequests 允许您将 Requests 与 Gevent 一起使用以进行异步 HTTP 请求很容易。


如您所见,当您处于更高级别并通过 webdriver 与浏览器交互时,您不必担心到达服务器以获取数据的实际请求。这使得自动化很容易,但可能会非常缓慢。当你进入低级自动化时,你有更多的选择来加快速度,但实现的复杂性增长得非常快。另外,想想这种解决方案有多可靠。所以可能会坚持“黑盒”解决方案并留在selenium


我也尝试使用以下方法解决问题:

但是由于不同的原因失败(可以为您提供相关的错误信息)。不过,所有这 3 个工具都应该有助于简化解决方案。

另见类似主题:

【讨论】:

  • 哇!所以是的 - 我选择 Selenium 的原因之一是因为我觉得他们可以对网站进行少量更改并非常容易地破坏我的脚本......但我也认识到运行虚拟网络浏览器有多慢。我也只是愉快地破解,并认为跳过所有帖子数据可能更容易,因为网站看起来有点乱。您如何确定需要填充哪些字段?
  • @tbondwilkinson 完全正确。我使用了 chrome 提供的浏览器开发工具。如果是 Firefox,您可以使用 firebug。网络抓取几乎必须具备。
  • 感谢@alecxe,我通过使用 lxml 解析器获得了 1.5 倍的速度提升,通过使用 SoupStrainer 仅限制 div 获得了 10 倍的速度,在我的情况下,大部分 html 没有用于任何用途。
【解决方案2】:

我将发布这个隐藏的宝石,希望它可以帮助某人,因为它对我有很大帮助:

只要确保将字符串对象传递给 BeautifulSoup 而不是字节。

如果您使用请求,请执行此操作

page = requests.get(some_url)
soup = BeautifulSoup(page.text, 'html.parser')

而不是这个

page = requests.get(some_url)
soup = BeautifulSoup(page.content, 'html.parser')

我不知道这背后的原因,参考文章的作者也不知道,但它确实使我的代码快了近 4 倍。

Speeding Up BeautifulSoup With Large XML Files, James Hodgkinson

【讨论】:

    【解决方案3】:

    根据beautifulsoup docs

    您可以通过安装 cchardet 库显着加快编码检测速度。

    假设您已经使用lxml 作为beautifulsoup(OP 就是)的解析器,您可以通过安装和导入cchardet 显着加快它(10x - link)。

    【讨论】:

    • 对我来说,它不是快 10 倍,而是快了大约 20%,这仍然很有帮助。奇怪的是字符检测减慢了它这么多......
    猜你喜欢
    • 1970-01-01
    • 2017-05-06
    • 1970-01-01
    • 2015-05-23
    • 2017-04-24
    • 2014-10-11
    • 2020-11-01
    • 2016-09-13
    • 1970-01-01
    相关资源
    最近更新 更多