专栏名称: 新语数据故事汇
《新语数据故事汇,数说新语》科普数据科学、讲述数据故事,深层次挖掘数据价值。
目录
相关文章推荐
51好读  ›  专栏  ›  新语数据故事汇

一文带您理解Selenium 和 BeautifulSoup:掌握数据抓取的核心技术

新语数据故事汇  · 公众号  ·  · 2024-10-07 18:37

正文

近期开发了一个数据抓取/数据格式化(指定网站内容)的小工具,起初设想使用 requests 和正则表达式的组合来实现,然而实际操作后发现无法顺利抓取数据。于是我转而尝试 requests 搭配 BeautifulSoup,但问题依旧,页面采用动态加载且有反爬机制,必须模拟浏览器行为才能获取数据。最后,经过多次调整,最终选择了 Python 驱动的 Selenium 方案,并结合正则表达式来格式化数据,成功实现了抓取任务。

接下来,将探讨使用 Selenium、BeautifulSoup 和 HTML 结构进行网页抓取的基础知识。无论您是有抱负的数据科学家,还是只是想扩展您的技术技能,这些知识都将成为您网页抓取之旅的基础。

HTML:Web 的支柱

HTML(超文本标记语言)是创建网页的标准语言。它由一系列定义网站结构、布局和内容的元素组成。

这些元素由标签表示,例如

表示段落、

表示标题、 表示超链接。通过了解这些标签的组织方式,您可以识别在网页抓取过程中要提取的相关内容。

每个 HTML 文件都由构成其底层结构的几个基本组件组成。 这些组件以嵌套的层次结构组织,文档的内容包含在这些标签中。

 <html> <head>     <title>您的页面标题title> head> <body>      body> html>
  1. :此声明定义所使用的 HTML 文档类型和版本。 它可帮助浏览器了解如何正确呈现页面。

  2. :这是页面的根元素,它包含文档的所有其他元素。

  3. :head 元素包含有关文档的元信息。

  4. :title元素指定网页的标题,显示在浏览器的标题栏或选项卡中。

  5. :body元素包含网页的实际内容,如文本、图像和多媒体。

除了构成 HTML 文件基本结构的标签外,还有许多其他标签用于在网页上创建不同类型的内容。 以下是您会遇到的一些最常见的 HTML 标签:

  1. :这些是标题标签,用于在内容中创建标题和副标题。

    是最大和最重要的,而是
    最小和最不重要的。

  2. :段落标签用于创建文本段落。它会自动在段落前后添加边距,从而在文本块之间提供清晰的分隔。

  3. :锚标记用于创建超链接,使用户可以在网页之间导航。该 href 属性指定链接的目标 URL。

    :这些标签分别用于创建无序列表(项目符号)和有序列表(编号)。列表中的每个项目都包含在一个
  4. 标签中。

  5. :table 标签用于创建表格,以行和列的形式显示数据。它通常与其他标签(如 (表格行)、

    (表格标题)和 (表格数据单元格))一起使用。

    每个 HTML 元素可以具有各种属性,这些属性可提供附加信息或修改其行为。网页抓取中最常用的一些属性是 class id href 。和通常 class id 用于定位和获取感兴趣的信息。 href 用于浏览不同的页面。

    • class:该class属性允许您将一个或多个 CSS 类分配给 HTML 元素

    class

    ="highlighted important" >
    本段落class属性有两个类:“highlighted”和“important”。p>
    • id:该id属性用于唯一标识 HTML 文档中的元素。每个id值在整个文档中都必须是唯一的。

    <div id = "main-content" >此div具有唯一ID:“main-content”div>
    • href :该 href 属性主要与 (anchor) 标签一起使用 ,指定超链接的目标 URL。当用户点击链接时,浏览器将导航到指定的 URL。

    <a href= "https://www.baidu.com">访问百度a>

    网络数据抓取主要有三种方式:

    1. 静态抓取 :这种方式仅解析 HTML,忽略 JavaScript。它不需要浏览器即可从服务器提取网页,获取的内容就是页面源代码中看到的静态部分。通过提供的 URL,您可以直接获取 HTML 结构进行剪切和解析。这也是它被称为“静态抓取”的原因——只需获取并处理服务器返回的静态页面内容。

    2. API 抓取 :如今很多网站的架构是 API + JS 的组合。通过分析页面结构,可以找到 API 地址和请求方法,直接向服务器发送请求,获取结构化的 JSON 数据。这种方式最为简便,因为返回的数据是格式化的json 或xml 数据。

    3. 动态抓取 :这种方式使用浏览器,可以读取通过 JavaScript 生成或修改的内容。简而言之,动态抓取模拟真实用户操作——像用户一样发送输入、改变页面或点击按钮。有时,还需要自动化浏览器操作来获取特定内容。对于此类任务,通常需要使用 Selenium WebDriver 来模拟用户行为。

    这三种方式各有优劣,选择哪种方式取决于目标网站的技术架构和防爬策略。

    使用 BeautifulSoup 进行静态网页抓取

    Beautifulsoup 是一个使用 HTML/XML 解析器并将网页/html/xml 转换为标签、元素、属性和值的树的 Python 库。

    安装BeautifulSoup

    向想要抓取的网页的 URL 发送一个 HTTP GET 请求,服务器将返回包含 HTML 内容的响应,使用 Python 的 requests 库来请求;使用BeautifulSoup加载解析html。

    from bs4 import BeautifulSoupimport requestsurl='https://bot.sannysoft.com/'html_content = requests.get(url).textsoup = BeautifulSoup
    
    
    
    
        
    (html_content, "html")print(soup.prettify())

    可以通过 .find([element_tag]) 方法轻松从 HTML 结构中提取出我们感兴趣的内容——用于查找单个元素;如果有多个元素,可以使用 .findAll([element_tag]) 方法。

    table_ele=soup.find('table')tres=table_ele.findAll('tr')for tr in tres:  tdes=tr.findAll('td')  if len(tdes)>1:    print(tdes[0].text,':',tdes[1].text)


    使用 Selenium 进行动态网页抓取

    Selenium 用于自动化网页应用程序操作。它允许您像人类用户一样打开浏览器并执行任务,例如点击按钮和在网站上搜索特定信息。

    安装Selenium和驱动,需要一个驱动程序来与浏览器进行交互。

    1. 安装 Selenium :在命令提示符或终端中运行以下命令: pip install selenium

    2. 下载驱动程序 :需要浏览器的驱动程序,让 Selenium 能够与浏览器交互。建议使用chrome,检查您的 Google Chrome 版本,然后在 https://developer.chrome.com/docs/chromedriver/downloads?hl=zh-cn 下载对应的 Chromedriver。

    下面是一个简单的示例:

    import subprocessimport threadingfrom selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.common.by import Byimport time

    def openChrome(): cmd = '"C:\Program Files\Google\Chrome\Application\chrome.exe" ' \ '--remote-debugging-port=9222 ' \ '--user-data-dir="d:\selenium\ChromeProfile" ' \ ' --disable-popup-blocking --disable-gpu --safebrowsing-disable-download-protection ' sub_chrome_process = subprocess.Popen(cmd, shell=True)

    thread = threading.Thread(target=openChrome)thread.start()time.sleep(2)print(f"{openChrome =}")
    def getDriver(): chrome_options = Options() chrome_options.add_argument('--ignore-certificate-errors') chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") driver = webdriver.Chrome(options=chrome_options) """params = { "behavior": "allow", "downloadPath": download_dir # 设置新的下载路径 } driver.execute_cdp_cmd("Page.setDownloadBehavior", params) driver.execute_cdp_cmd("Security.setIgnoreCertificateErrors", {"ignore": True})""" return driver

    driver =getDriver()driver.get("https://bot.sannysoft.com/")
    print("*"*50)table_ele=driver.find_element(By.TAG_NAME,"table")for tr_ele in table_ele.find_elements(By.TAG_NAME,"tr"): tdes_ele=tr_ele.find_elements(By.TAG_NAME,'td') if len(tdes_ele)>1: print(tdes_ele[0].text.replace('\n',''),":",tdes_ele[1].text)print("*"*50)
    scroll_step = 200 # 每次滚动的像素pause_time = 0.3 # 每次滚动后的等待时间(秒)current_height = driver.execute_script("return document.body.scrollHeight") # 获取当前页面总高度# 逐步向下滚动for j in range(0, current_height, scroll_step): driver.execute_script(f"window.scrollBy(0, {scroll_step});") time.sleep(pause_time)

    这段代码的逻辑和步骤如下:

    1. 导入必要的库

    • subprocess :用于在 Python 中启动新进程。

    • threading :用于创建和管理线程。

    • selenium.webdriver :用于与浏览器进行交互。

    • time :用于时间延迟。

  6. 定义 openChrome 函数:

    • 构造一个命令行字符串 cmd,用于启动 Google Chrome 浏览器,并指定一些启动选项,注意开启远程模式:--remote-debugging-port=9222:开启远程调试,允许其他程序(如 Selenium)连接到这个端口。

    • 使用 subprocess.Popen 启动 Chrome 浏览器进程。

  7. 创建并启动线程:

    • 使用 threading.Thread 创建一个新线程,目标是运行 openChrome 函数。

    • 启动该线程,并在启动后等待 2 秒,以确保 Chrome 浏览器启动完成。

  8. 定义 getDriver 函数:

    • 创建一个 Options 对象,配置 Chrome 的启动选项。

    • 添加忽略证书错误的选项:chrome_options.add_argument('--ignore-certificate-errors')。

    • 设置调试地址,使 Selenium 能够连接到已启动的 Chrome 实例:chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")。

    • 使用配置的选项创建 Chrome WebDriver 实例并返回。

  9. 获取 WebDriver:

    • 调用 getDriver 函数获取 WebDriver 并访问指定的 URL(这里是一个示例网页 " https://bot.sannysoft.com/" )。

  10. 打印表格数据:

    • 使用 find_element 和 find_elements 方法找到网页中的 元素及其子元素 和

    • 遍历表格的每一行,如果该行中有多个单元格(

    • ),则打印第一个和第二个单元格的文本内容。
    • 滚动页面:

      • 设置每次滚动的像素和等待时间。

      • 获取当前页面的总高度。

      • 使用 execute_script 方法逐步向下滚动页面,直到达到页面底部。

      通过这段代码,您可以实现自动化浏览器操作,抓取网页上的表格数据,并进行页面滚动,以获取更多信息。

      特别注意的如果是直接打开的chrome 浏览器的话(driver =webdriver.Chrome()) ,WebDriver 标记为True,有较多的网站防爬取判断,被网站拦截掉,可以直接先开启远程模式打开chrome ,然后创建dirver连接到debuggerAddress 上驱动chrome 。


      以下是实践过程中点滴总结:

      • 网站数据爬取是全栈的技术活,需要了解HTTP、HTML、Python、Selenium、BeautifulSoup、正则表达式、验证码识别等。

      • 从网站进行数据爬取和解析本质上可以理解网页的非格式化数据逆向为格式化数据的过程;不同完整需要不同的方式和策略,特别是网站防爬设置、流量控制,需要控制速率、模拟一些真人操作的一些行为。

      • 内容的解析和格式化才是最具挑战性的工作,特别是解析文档类的数据,正则表达式的功底是需要的。


      网站数据爬取是一项全栈技术,涉及 HTTP、HTML、Python、Selenium、BeautifulSoup 和正则表达式等工具。成功抓取依赖于对网页结构的理解、绕过防爬机制及流量控制。解析与格式化是其中最具挑战的环节,特别是文档类数据的处理,需要扎实的正则表达式能力。

      继续滑动看下一个
      新语数据故事汇
      向上滑动看下一个
      '; videoPlaceHolderSpan.style.cssText = "width: " + obj.w + "px !important;"; insertAfter(videoPlaceHolderSpan, a); var mid = "2247490121" || "" || ""; var biz = "Mzg2NzgzMjU5MA==" || ""; var sessionid = "" || "svr_34e0146f6fc"; var idx = "1" || ""; var hitInfos = [ ]; (function setHitStyle(parentNode, copyIframe, index, vid) { var ret = (hitInfos || []).find(function (info) { return info.video_id === vid; } ); if (!ret) return; var ori = ret.ori_status; var hit_biz_headimg = ret.hit_biz_headimg + '/64'; var hit_nickname = ret.hit_nickname; var hit_username = ret.hit_username; var sourceBiz = ret.hit_bizuin; var selfUserName = "gh_dba8fcd77b28"; if (ori === 2 && selfUserName !== hit_username) { var videoBar = document.createElement('div'); var videoBarHtml = ''; videoBar.innerHTML = videoBarHtml; var spanContainer = document.getElementById('js_mp_video_container_' + index); if (spanContainer) { spanContainer.parentNode.insertBefore(videoBar, spanContainer); } else if (parentNode.contains && parentNode.contains(copyIframe)) { parentNode.insertBefore(videoBar, copyIframe); } else { parentNode.insertBefore(videoBar, parentNode.firstElementChild); } var avatorEle = document.getElementById(hit_biz_headimg + index); var avatorSrc = avatorEle.dataset.src; console.log('avatorSrc' + avatorSrc); if (ret.hit_biz_headimg) { avatorEle.style.backgroundImage = 'url(' + avatorSrc + ')'; } } })(a.parentNode, a, i, vid); a.style.cssText += ";width: " + obj.w + "px !important;"; a.setAttribute("width", obj.w); if (window.__zoom != 1) { a.style.display = "block"; videoPlaceHolderSpan.style.display = "none"; a.setAttribute("_ratio", obj.ratio); a.setAttribute("_vid", vid); } else { videoPlaceHolderSpan.style.cssText += "height: " + (obj.h - obj.sdh) + "px !important;margin-bottom: " + obj.sdh + "px !important;"; a.style.cssText += "height: " + obj.h + "px !important;"; a.setAttribute("height", obj.h); } a.setAttribute("data-vh", obj.vh); a.setAttribute("data-vw", obj.vw); if (a.getAttribute("data-mpvid")) { a.setAttribute("data-src", location.protocol + "//mp.weixin.qq.com/mp/readtemplate?t=pages/video_player_tmpl&auto=0&vid=" + vid); } else { a.setAttribute("data-src", location.protocol + "//v.qq.com/iframe/player.html?vid=" + vid + "&width=" + obj.vw + "&height=" + obj.vh + "&auto=0"); } } })(); (function () { if (window.__zoom != 1) { if (!window.__second_open__) { document.getElementById('page-content').style.zoom = window.__zoom; var a = document.getElementById('activity-name'); var b = document.getElementById('meta_content'); if (!!a) { a.style.zoom = 1 / window.__zoom; } if (!!b) { b.style.zoom = 1 / window.__zoom; } } var images = document.getElementsByTagName('img'); for (var i = 0, il = images.length; i < il; i++) { if (window.__second_open__ && images[i].getAttribute('__sec_open_place_holder__')) { continue; } images[i].style.zoom = 1 / window.__zoom; } var iframe = document.getElementsByTagName('iframe'); for (var i = 0, il = iframe.length; i < il; i++) { if (window.__second_open__ && iframe[i].getAttribute('__sec_open_place_holder__')) { continue; } var a = iframe[i]; a.style.zoom = 1 / window.__zoom; var src_ = a.getAttribute('data-src') || ""; if (!/^http(s)*\:\/\/v\.qq\.com\/iframe\/(preview|player)\.html\?/.test(src_) && !/^http(s)*\:\/\/mp\.weixin\.qq\.com\/mp\/readtemplate\?t=pages\/video_player_tmpl/.test(src_) ) { continue; } var ratio = a.getAttribute("_ratio"); var vid = a.getAttribute("_vid"); a.removeAttribute("_ratio"); a.removeAttribute("_vid"); var vw = a.offsetWidth - (getOuterW(a) || 0); var vh = vw / ratio; var h = vh + (getOuterH(a) || 0) a.style.cssText += "height: " + h + "px !important;" a.setAttribute("height", h); if (/^http(s)*\:\/\/v\.qq\.com\/iframe\/(preview|player)\.html\?/.test(src_)) { a.setAttribute("data-src", location.protocol + "//v.qq.com/iframe/player.html?vid=" + vid + "&width=" + vw + "&height=" + vh + "&auto=0"); } a.style.display = "none"; var parent = a.parentNode; if (!parent) { continue; } for (var j = 0, jl = parent.children.length; j < jl; j++) { var child = parent.children[j]; if (child.className.indexOf("js_img_placeholder") >= 0 && child.getAttribute("data-vid") == vid) { child.style.cssText += "height: " + h + "px !important;"; child.style.display = ""; } } } } })(); })(); var anchor_tree_msg = '';







请到「今天看啥」查看全文