【问题标题】:Trouble fetching some title from a webpage无法从网页中获取某些标题
【发布时间】:2018-10-06 18:33:31
【问题描述】:

我用 php 编写了一个脚本来从一个 title网页。当我执行以下脚本时,出现以下错误:

注意:尝试在第 16 行获取 C:\xampp\htdocs\runco​​de\testfile.php 中非对象的属性“nodeValue”。

Link to that site

我尝试过的脚本:

<?php
    function get_content($url){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_exec($ch);
        $htmlContent = curl_exec($ch);
        curl_close($ch);
        return $htmlContent;
    }
    $link = "https://www.purplle.com/search?q=hair%20fall%20shamboo"; 
    $xml = get_content($link);
    $dom = @DOMDocument::loadHTML($xml);
    $xpath = new DOMXPath($dom);
    $title = $xpath->query('//h1[@class="br-hdng"]/span')->item(0)->nodeValue;
    echo "{$title}";
?>

我的预期输出是:

hair fall shamboo

虽然我在上述脚本中使用的xpath 似乎是正确的,但我在此处粘贴了可以找到title 的html 元素的相关部分:

<h1 _ngcontent-c0="" class="br-hdng"><span _ngcontent-c0="" class="pr dib">hair fall shamboo<!----></span></h1>

PostScript: 我希望解析的title 被动态加载。由于我是 php 新手,我不明白我尝试的方式是否准确。如果不是那我该怎么办?

以下是我使用两种不同语言创建的脚本,发现它们的工作原理很神奇。

我使用javascript获得了成功:

const puppeteer = require('puppeteer');
function run () {
    return new Promise(async (resolve, reject) => {
        try {
            const browser = await puppeteer.launch();
            const page = await browser.newPage();
            await page.goto("https://www.purplle.com/search?q=hair%20fall%20shamboo");
            let urls = await page.evaluate(() => {
            let items = document.querySelector('h1.br-hdng span');
            return items.innerText;;
            })
            browser.close();
            return resolve(urls);
        } catch (e) {
            return reject(e);
        }
    })
}
run().then(console.log).catch(console.error);

再次,我使用python 获得了成功:

import requests_html

with requests_html.HTMLSession() as session:
    r = session.get('https://www.purplle.com/search?q=hair%20fall%20shamboo')
    r.html.render()
    item = r.html.find("h1.br-hdng span",first=True).text
    print(item)

php 怎么了?

【问题讨论】:

  • $xml = get_content($link); 也许你想在这里使用file_get_contents()
  • 您建议的更改带来了同样的错误@user9741470。
  • 如果内容不在 HTML 中,则无法通过解析 HTML 获取。
  • @Topto 你知道问题是什么:动态内容。 CURL 不运行 Js,但你可以尝试 php-phantomjs,但我不推荐它,因为我从未使用过它。 PHP 是一种很棒的服务器端语言,但不擅长网络抓取。如果您打算将其用于网络抓取,我认为您应该坚持使用 Python、Ruby 等通用语言。
  • @karanthakkar 好吧,从技术上讲,这仍然是动态内容,但可能是由后端脚本而不是 JavaScript 创建的。我认为 OP 对这个特定元素不感兴趣,他们只是想学习如何使用 PHP 抓取动态内容。如果是这种情况,php-phantomjs 图书馆应该这样做,但我没有这方面的经验,所以我不会发布答案。

标签: php curl web-scraping domdocument


【解决方案1】:

很可能您的代码存在比我在此答案中涵盖的更多的问题,但我看到的最突出的问题如下:

DOMDocument::loadHTML() 不是静态方法,而是实例方法(返回布尔值)。您应该首先创建一个DOMDocument 的实例,然后在该实例上调用loadHTML()

$dom = new DOMDocument;
$dom->loadHTML($xml);

但是,由于您在该特定行上使用 @ 运算符抑制了错误,因此您不会收到关于此的警告。虽然很常见的是错误抑制运算符 @ 用于抑制 HTML 验证错误,像这样,您应该考虑使用 libxml_use_internal_errors()1 代替,因为这不会抑制一般 PHP错误。

$dom = new DOMDocument;
$oldSetting = libxml_use_internal_errors(true);
$dom->loadHTML($xml);
libxml_use_internal_errors($oldSetting);

最后一点:
如果您的 PHP 安装配置为允许通过配置设置 allow_url_fopen 加载 URL,则可以使用 DOMDocument::loadHTMLFile() 从 URL 直接加载 DOM 文档(无需 cURL)。请注意,出于安全原因,此设置通常会被禁用,因此如果您打算使用它,请谨慎使用。


这是一个简单的测试用例,应该可以按预期工作:

<?php

$html = '
<html>
<head>
  <title>DOMDocument test-case</title>
</head>
<body>
  <div class="dummy-container">
    <h1 _ngcontent-c0="" class="br-hdng"><span _ngcontent-c0="" class="pr dib">hair fall shamboo<!----></span></h1>
  </div>
</body>';

$dom = new DOMDocument;

$oldSetting = libxml_use_internal_errors(true);
$dom->loadHTML( $html );
libxml_use_internal_errors($oldSetting);

$xpath = new DOMXPath( $dom );
$title = $xpath->query( '//h1[@class="br-hdng"]/span' )->item( 0 )->nodeValue;
echo $title;

See this example interpreted online on 3v4l.org

您应该将$html 的内容替换为您的get_content() 调用的输出。如果它不起作用,那么:

  1. 使用cURL 获取 HTML 时出现问题(例如,在加载到 DOMDocument 之前执行var_dump( $html );,以查看您检索的内容),或者...

    李>
  2. 也许您在命名空间内工作,在这种情况下,您应该在 DOMDocumentDOMXPath 之前添加反斜杠,即:new \DOMDocument;new \DOMXPath( $dom );


1. LibXML 是 DOMDocument 用来解析 XML/HTML 文档的 XML 库。

【讨论】:

  • 我确实尝试了您建议的选项,最后遇到了与我现有脚本@Decent Dabbler 相同的错误。你确定还有任何选择可以得到那个头衔吗?我真的受够了 php。
  • @Topto 我已经用一个工作示例更新了我的答案,您可以自己测试并在线查看解释。进行必要的调整,看看它现在是否有效。
  • 您正在处理@Decent Dabbler 的静态内容,而我想从真实网络中解析的内容是动态内容,当您打开该页面时,它会在几秒钟后加载。这就是问题出现的地方。顺便说一句,我尝试了所有可能的组合,但没有骰子。
  • @Topto 我明白了...我没有意识到这一点(我倾向于不点击问题中不熟悉的链接来查看其内容;我的错)。很遗憾,DOMDocument 无法处理动态内容。
【解决方案2】:

那么php有什么问题呢?

php 不运行 javascript。据推测,您的 javascript 代码中的 puppeteer 以及您的 python 代码中的 requests_html 都运行 javascript。

您的问题是此页面使用 javascript 加载 br-hdng 标题和产品,它根本不是 HTML 的一部分。它实际上都是从 https://www.purplle.com/api/shop/itemsv3 加载的,带有一堆 GET 参数。您需要在这里进行 JSON 解析,而不是 HTML 解析 :) 但在您可以访问该 api 之前,您需要搜索页面提供的 cookie,并且 搜索字符串必须与 api 搜索字符串匹配(否则api 只会返回错误),请检查:

<?php
declare(strict_types = 0);
header ( "Content-Type: text/plain;charset=UTF-8" );
$ch = curl_init ();
curl_setopt_array ( $ch, array (
        CURLOPT_ENCODING => '',
        CURLOPT_COOKIEFILE => '', // enables cookie handling without saving them anywhere. this page requires cookie handling.
        CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0', // 'libcurl/? PHP/' . PHP_VERSION, // many websites block requests without a useragent
        CURLOPT_RETURNTRANSFER => 1 
) );
// we don't care what's on this page, we just need to fetch it to create a cookie session.
$search_query = 'hair fall shamboo';
curl_setopt ( $ch, CURLOPT_URL, 'https://www.purplle.com/search?q=' . rawurlencode ( $search_query ) );
curL_exec ( $ch );
$url = 'https://www.purplle.com/api/shop/itemsv3?' . http_build_query ( array (
        'list_type' => 'search',
        'custom' => '',
        'list_type_value' => $search_query,
        'page' => '1',
        'sort_by' => 'rel',
        'elite' => '0' 
) );
// $url = 'https://www.purplle.com/api/shop/itemsv3?list_type=search&custom=&list_type_value=hair%20fall%20shamboo&page=1&sort_by=rel&elite=0';
// $out = tmpfile ();
// curl_setopt_array ( $ch, array (
// CURLOPT_HTTPHEADER => array (
// 'Accept: application/json, text/plain, */*',
// 'Accept-Language: en-US,en;q=0.5',
// 'Referer: https://www.purplle.com/search?q=hair%20fall%20shamboo',
// // Cookie: __cfduid=d3199415b5ce18cbff2779802b1f843331544901552; csrftoken=f8f18b5deae92972f63343e13c6a460b; purpllesession=hedxkc%2FkdGye%2BYi6ebmJktUN1LeqA5rdVXu96%2F0j0yqtP2xZ8LfwpK8daXqPSkeZulO9ZvqpMYXTmY8oMD03VcG9vdKGBm30R9fU%2FQygtXBFhZvfvsu0scyaL3FqHbePp2zG45MevWU961eg82KAkCuHk0qFM8URQBRyYV5gg8TeqnTPgI3tF87H5nJ%2BmfO4pn%2BRWmIuWXvgNXAO%2F8GEaH6lJVl17QZm9c5vwi10OYeLfmSdIMy6V2Pp0ZjLTzuFw2de5jpR0zsbHHKZ0C2e548PiDl3taHIE5wuZO4HYIeXUqTpE98%2Fo3kztoU1bTlXGZgu%2FxVQ3EWLRFWQ2t57UawA%2FuERlD8vvOyFGbYHGAWVxgFTR%2FObAhFLHns5kqoj; _autm30d=null; visitorppl=NZ5tqQpGlFYWg2MrDl1302113161544901552; session_initiated=Direct; _tmpsess=1; token=desktop_5c1553b07c61c_7955_16122018; __uzma=5c1553b085a480.63440826; __uzmb=1544901552; __uzmc=632121030774; __uzmd=1544901552
// 'Connection: keep-alive'
// ),
// // CURLOPT_CONNECT_TO=>array('www.purplle.com:443:dumpinput.ratma.net:80'),
// CURLOPT_STDERR => $out,
// CURLOPT_VERBOSE => 1
// ) );
// var_dump ( $url );
curl_setopt ( $ch, CURLOPT_URL, $url );
$json = curl_exec ( $ch );
$data = json_decode ( $json, true );

// var_dump ($json, $data );
$title = $data ['list_title'];
echo 'title: ' . $title . "\n";
foreach ( $data ['items'] as $item ) {
    echo "name: ", $item ['name'], "\n";
}

输出:

title: hair fall shamboo
name: VLCC Hair fall Shampoo 350 ML (Buy1 Get1) & Ayurveda Hair Oil Combo (470 ml)
name: Biotique Bio Kelp Protein Shampoo For Falling Hair (190 ml)
name: Biotique Fresh Texture Shampoo - Bio Henna Leaf (120 ml)
name: Good Vibes Scalp Purifying Shampoo -Neem And Aloe Vera (200 ml)
name: Khadi Shikakai Sat Hair Cleanser Scalp Therapy (210 ml) By Swati Gramodyog
name: Good Vibes Apple Cider Vinegar Shampoo (120 ml)
name: Good Vibes Refreshing Shampoo - Green Apple (200 ml)
name: Good Vibes Hydrating Shampoo -Marigold (200 ml)
name: Alps Goodness Smoothening Shampoo - Keratin (50 ml)
name: Alps Goodness Softening Shampoo - Coconut & Almond (50 ml)
name: Alps Goodness Split End Control Shampoo - Coconut, Garlic & Shea Butter (50 ml)
name: Passion Indulge Papain Shampoo & Conditioner For Soft & Shiny Hair (200 ml + 100 ml)
name: Good Vibes Apple Cider Vinegar Shampoo (200 ml)
name: Alps Goodness Split End Control Shampoo - Coconut, Garlic & Shea Butter (200 ml)
name: Alps Goodness Nourishing Shampoo - Argan Oil & Olive (200 ml)
name: Alps Goodness Moisturizing Shampoo - Ginger & Egg (200 ml)
name: Alps Goodness Conditioning Shampoo - Pure Honey (200 ml)
name: Alps Goodness Hydrating Shampoo - Tea Tree (200 ml)
name: Alps Goodness Smoothening Shampoo - Keratin (200 ml)
name: Alps Goodness Softening Shampoo - Coconut & Almond (200 ml)
name: Good Vibes Scalp Purifying Shampoo -Neem And Aloe Vera (120 ml)
name: Good Vibes Hydrating Shampoo - Marigold (120 ml)
name: Alps Goodness Conditioning Shampoo - Pure Honey (50 ml)
name: Alps Goodness Moisturizing Shampoo - Ginger & Egg (50 ml)

【讨论】:

  • 感谢您的回答@hanshenrik。那个网站的数据对我来说完全没用。我只想知道如何从网页中获取 javascript 加密项目,因为它们中的一些不提供任何 api 也不提供通过开发工具生成的任何 json 加载链接。最重要的是,无论是否有任何浏览器模拟器发挥作用来完成任务,我都想抓住贴在帖子中 URL 的标题。已经有两个使用主链接的两种不同语言的演示。
  • @asmitu 显示给搜索引擎的那个页面的标题实际上是Purplle.com: Buy Cosmetic Products &amp; Beauty Products Online In India,你通过检查&lt;meta property="og:title" 得到,这是你想要的标题吗?在这种情况下,xpath 将是 //meta[@property="og:title"]
猜你喜欢
  • 2019-01-05
  • 1970-01-01
  • 2020-08-30
  • 1970-01-01
  • 1970-01-01
  • 2019-01-05
  • 1970-01-01
  • 1970-01-01
  • 2020-06-07
相关资源
最近更新 更多