使用 request(),返回的页面还不包含需要的数据——而是返回不完整的页面。我如何“等待”?

Ano*_*ous 6 request node.js web-scraping express

我正在尝试从 carjam.co.nz 中提取年份、品牌、型号、颜色和车牌号。我正在抓取的 URL 示例是https://www.carjam.co.nz/car/?plate=JKY242

如果最近请求了牌照,则响应将是包含车辆详细信息的 HTML 文档。

最近请求板详细信息的结果。 最近请求板详细信息的结果。

如果最近没有请求车牌详细信息(大多数车牌都是这种情况),则响应是带有“尝试获取一些车辆数据”的 HTML 文档。我猜这个页面会在从数据库中获取信息时显示,然后重新加载页面以显示车辆详细信息。这似乎是在服务器端呈现的,我看不到任何 AJAX 请求。

每个结果的 URL 都相同。

最近未请求板详细信息的结果。 最近未请求车辆的结果。

我如何“等待”正确的信息?

我在request带有 Express 服务器的 Node.js 上使用(我知道已弃用,但这是我最习惯使用的)。

我的(非常减少的)代码:

app.get("/:numberPlate", (req, res) => {
  request("https://www.carjam.co.nz/car/?plate=" + req.params.numberPlate, function(error, response, body) {
    const $ = cheerio.load(body);
    res.status(200).send(JSON.stringify({
      year: $("[data-key=year_of_manufacture]").next().html(),
      make: toTitleCase($("[data-key=make]").next().html()),
      model: toTitleCase($("[data-key=model]").next().html()),
      colour: toTitleCase($("[data-key=main_colour]").next().html()),
  }));
  }
}
Run Code Online (Sandbox Code Playgroud)

我考虑过:

  • 发出请求并丢弃它,休眠 2 - 3 秒,然后发出第二个请求。这种方法的优点是每个请求都可以工作。缺点是每个请求需要 2 - 3 秒(太慢)。
  • 发出请求并检查正文是否包含“试图获取一些车辆数据”。如果是这样,请休眠几秒钟,发出另一个请求并对第二个请求的结果采取行动(但如何?)。

我确信这是一个常见问题,答案很简单,但我没有足够的经验来自己解决这个问题,或者确切地知道谷歌是什么!


测试:新西兰有“ABC123”格式的数字位置——三个字母,三个数字。这些是按字母顺序发布的,目前我们没有超过 NLU999 的内容(不包括自定义号牌、乱序发行的号牌等)。

要重现“试图获取一些车辆数据”,您每次都需要找到一个新的车牌——序列中早于 NLU999 的大多数车牌应该可以工作。

此代码段应生成有效的车牌。

console.log(Math.random().toString(36).replace(/[^a-n]+/g, '').substr(0, 1).toUpperCase() + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 2).toUpperCase() + Math.floor(Math.random() * 10).toString() + Math.floor(Math.random() * 10).toString() + Math.floor(Math.random() * 10).toString());
Run Code Online (Sandbox Code Playgroud)


2021 年 5 月 5 日更新

经过进一步思考,这个伪代码可能就是我所追求的——但不确定如何实际实现。

request(url) {
  if (url body contains "Trying to get some vehicle data") {
    wait(2 seconds)
    request(url again) {
      return second_result
    }
  } else {
    return first_result
  }
}
then
  process(first_result or second_result)
Run Code Online (Sandbox Code Playgroud)

我的困难是:我习惯了格式request().then(),直接从请求中采取行动。

假设这种方法是正确的,我将如何进行以下操作?

  1. 发送请求,然后
  2. 评估响应,然后
  3. 传递此响应,发送另一个请求然后传递该响应
  4. 过程响应

Ber*_*tel 1

From this javascript file, the website loads the page every X seconds if the data is not found with a max retry set to 10. Also the refresh value in seconds is retrieved from the Refresh http header value.

You can reproduce this flow, so that you have exactly the same behaviour as the frontend code.

In the following example I'm using axios

const axios = require("axios");
const cheerio = require("cheerio");

const rootUrl = "https://www.carjam.co.nz/car/";
const plate = "NLU975";
const maxRetry = 10;
const waitingString = "Waiting for a few more things";

async function getResult() {
  return axios.get(rootUrl, {
    params: {
      plate: plate,
    },
  });
}

async function processRetry(result) {
  const refreshSeconds = parseInt(result.headers["refresh"]);
  var retryCount = 0;
  while (retryCount < maxRetry) {
    console.log(
      `retry: ${retryCount} time, waiting for ${refreshSeconds} second(s)`
    );
    retryCount++;
    await timeout(refreshSeconds * 1000);
    result = await getResult();
    if (!result.data.includes(waitingString)) {
      break;
    }
  }
  return result;
}

(async () => {
  var result = await getResult();
  if (result.data.includes(waitingString)) {
    result = await processRetry(result);
  }
  const $ = cheerio.load(result.data);
  console.log({
    year: $("[data-key=year_of_manufacture]").next().html(),
    make: $("[data-key=make]").next().html(),
    model: $("[data-key=model]").next().html(),
    colour: $("[data-key=main_colour]").next().html(),
  });
})();

function timeout(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
Run Code Online (Sandbox Code Playgroud)

repl.it link: https://replit.com/@bertrandmartel/ScrapeCarJam

Sample output:

retry: 0 time, waiting for 1 second(s)
retry: 1 time, waiting for 1 second(s)
retry: 2 time, waiting for 1 second(s)
{ year: 'XXXX', make: 'XXXXXX', model: 'XX', colour: 'XXXX' }
Run Code Online (Sandbox Code Playgroud)

It uses async/await instead of promise.

Note that request is deprecated