CoderCastrov logo
CoderCastrov
Парсер веб-страниц

Как создать веб-парсер? — Scraping-bot.io

Как создать веб-парсер? — Scraping-bot.io
просмотров
7 мин чтение
#Парсер веб-страниц

В эру больших данных, веб-парсинг является настоящим спасителем. Чтобы еще больше сэкономить время, вы можете использовать ScrapingBot вместе с веб-парсером.

Что такое веб-парсер?

Парсер, или паук, - это интернет-бот, который индексирует и посещает каждый URL-адрес, с которым он сталкивается. Его целью является посещение веб-сайта от начала до конца, определение содержимого каждой веб-страницы и возможность нахождения местоположения любой информации. Самые известные веб-парсеры - это поисковые системы, например, GoogleBot. Когда веб-сайт находится в сети, эти парсеры посещают его и считывают его содержимое, чтобы отобразить его на соответствующих страницах результатов поиска.

Как работает веб-паук?

Начиная с корневого URL или набора записей, паук получает веб-страницы и находит другие URL-адреса для посещения, называемые сидами, на этой странице. Все найденные сиды добавляются в список URL-адресов для посещения. Этот список называется горизонтом. Паук организует ссылки в две нити: ссылки для посещения и уже посещенные ссылки. Он будет продолжать посещать ссылки, пока горизонт не станет пустым.

Поскольку список сидов может быть очень длинным, паук должен организовывать их, руководствуясь несколькими критериями, и определять приоритеты, какие из них посещать первыми и повторно. Чтобы узнать, какие страницы являются более важными для парсинга, бот будет учитывать, сколько ссылок ведет на этот URL и как часто его посещают обычные пользователи.

В чем разница между веб-скрейпером и веб-краулером?

Краулинг, по определению, всегда подразумевает веб. Цель краулера - следовать по ссылкам для достижения множества страниц и анализировать их метаданные и содержимое.

Парсинг возможен и вне веба. Например, вы можете извлечь некоторую информацию из базы данных. Парсинг - это извлечение данных из веба или базы данных.

Зачем нужен веб-краулер?

С помощью веб-парсинга вы экономите огромное количество времени, автоматически получая нужную информацию, а не ища и копируя ее вручную. Однако вам все равно нужно парсить страницу за страницей. Веб-краулинг позволяет собирать, организовывать и посещать все страницы, присутствующие на корневой странице, с возможностью исключить некоторые ссылки. Корневой страницей может быть страница с результатами поиска или категория.

Например, вы можете выбрать категорию товаров или страницу с результатами поиска на Amazon в качестве входных данных и просканировать ее, чтобы извлечь все детали о продукте и ограничиться первыми 10 страницами с рекомендуемыми продуктами.

Как создать веб-сканер?

Первое, что вам нужно сделать, это потоки:

  • Посещенные URL-адреса
  • URL-адреса для посещения (очередь)

Чтобы избежать повторного сканирования одной и той же страницы, URL-адрес должен автоматически перемещаться в поток посещенных URL-адресов, как только вы закончили его сканировать. На каждой веб-странице вы найдете новые URL-адреса. Большинство из них будут добавлены в очередь, но некоторые из них могут не добавлять никакой ценности для вашей цели. Поэтому вам также нужно установить правила для URL-адресов, которые вас не интересуют.

Удаление дубликатов является важной частью сканирования веб-страниц. На некоторых веб-сайтах, особенно на электронной коммерции, одна веб-страница может иметь несколько URL-адресов. Чтобы вы могли спарсить эту страницу только один раз, лучший способ сделать это - найти канонический тег в коде. Все страницы с одинаковым содержимым будут иметь общий канонический URL-адрес, и это единственная ссылка, которую вам нужно будет сканировать и парсить.

Вот пример канонического тега в HTML:

Вот основные шаги для создания сканера:

  • Шаг 1: Добавьте один или несколько URL-адресов для посещения.
  • Шаг 2: Извлеките ссылку из URL-адресов для посещения и добавьте ее в поток посещенных URL-адресов.
  • Шаг 3: Получите содержимое страницы и спарсите интересующие вас данные с помощью ScrapingBot API.
  • Шаг 4: Разберите все URL-адреса, присутствующие на странице, и добавьте их в URL-адреса для посещения, если они соответствуют установленным вами правилам и не соответствуют ни одному из посещенных URL-адресов.
  • Шаг 5: Повторите шаги с 2 по 4, пока список URL-адресов для посещения не станет пустым.

NB: Шаги 1 и 2 должны быть синхронизированы.

Аналогично веб-парсингу, есть некоторые правила, которые нужно соблюдать, когда сканируется веб-сайт. Файл robots.txt указывает, какие области карты сайта не должны посещаться сканером. Кроме того, сканер должен избегать перегрузки веб-сайта, ограничивая скорость сканирования, чтобы сохранить хороший опыт для пользователей. В противном случае веб-сайт, с которого происходит парсинг, может решить заблокировать IP сканера или принять другие меры.

Вот пример сканера с использованием ScrapingBot API с двумя зависимостями: request и cheerio. Вам нужно использовать как минимум nodeJs 8 из-за использования await/async.

const request = require("request");
const util = require("util");
const rp = util.promisify(request);
const sleep = util.promisify(setTimeout);
const cheerio = require('cheerio');
const { URL } = require('url');

let seenLinks = {};
let rootNode = {};
let currentNode = {};
let linksQueue = [];
let printList = [];
let previousDepth = 0;
let maxCrawlingDepth = 5;
let options = null;
let mainDomain = null;
let mainParsedUrl = null;

class CreateLink {
  constructor(linkURL, depth, parent) {
    this.url = linkURL;
    this.depth = depth;
    this.parent = parent;
    this.children = [];
  }
}

// ваши учетные данные для ScrapingBot
let username = "yourUsername",
  apiKey = "yourApiKey",
  apiEndPoint = "http://api.scraping-bot.io/scrape/raw-html",
  auth = "Basic " + Buffer.from(username + ":" + apiKey).toString("base64");

let requestOptions = {
  method: 'POST',
  url: apiEndPoint,
  json: {
    url: "this will be replaced in the findLinks function", // опции ScrapingBot
    options: {
      useChrome: false, // если вы хотите использовать безголовый Chrome, ВНИМАНИЕ, для этой опции будет использоваться два вызова API
      premiumProxy: false, // если вы хотите использовать премиум-прокси для разблокировки Amazon, LinkedIn (потребуется 10 вызовов)
    }
  },
  headers: {
    Accept: 'application/json',
    Authorization: auth
  }
}

// Запуск приложения, здесь вы должны указать адрес, с которого хотите начать сканирование
// второй параметр - это глубина, с 1 он будет сканировать все ссылки, найденные на первой странице, но не находить ссылки на других страницах
// если вы установите 2, он будет сканировать все ссылки на первой странице и все ссылки на страницах второго уровня, будьте осторожны с этим на огромном веб-сайте, это будет представлять огромное количество страниц для сканирования
// рекомендуется ограничить до 5 уровней
crawlBFS("https://www.scraping-bot.io/", 1);

async function crawlBFS(startURL, maxDepth = 5) {
  try {
    mainParsedUrl = new URL(startURL);
  } catch (e) {
    console.log("URL is not valid", e);
    return;
  }
  mainDomain = mainParsedUrl.hostname;
  maxCrawlingDepth = maxDepth;
  startLinkObj = new CreateLink(startURL, 0, null);
  rootNode = currentNode = startLinkObj;
  addToLinkQueue(currentNode);
  await findLinks(currentNode);
}

async function findLinks(linkObj) {
  requestOptions.json.url = linkObj.url;
  console.log("Scraping URL : " + linkObj.url);
  let response;
  try {
    response = await rp(requestOptions);
    if (response.statusCode !== 200) {
      if (response.statusCode === 401 || response.statusCode === 405) {
        console.log("autentication failed check your credentials");
      } else {
        console.log("an error occurred check the URL" + response.statusCode, response.body);
      }
      return;
    }
    let $ = cheerio.load(response.body);
    let links = $('body').find('a').filter(function (i, el) {
      return $(this).attr('href') != null;
    }).map(function (i, x) {
      return $(this).attr('href');
    });

    if (links.length > 0) {
      links.map(function (i, x) {
        let reqLink = checkDomain(x);
        if (reqLink) {
          if (reqLink != linkObj.url) {
            newLinkObj = new CreateLink(reqLink, linkObj.depth + 1, linkObj);
            addToLinkQueue(newLinkObj);
          }
        }
      });
    } else {
      console.log("No more links found for " + requestOptions.url);
    }

    let nextLinkObj = getNextInQueue();
    if (nextLinkObj && nextLinkObj.depth <= maxCrawlingDepth) {
      let minimumWaitTime = 500; // полсекунды
      let maximumWaitTime = 5000; // максимум пять секунд
      let waitTime = Math.round(minimumWaitTime + (Math.random() * (maximumWaitTime - minimumWaitTime)));
      console.log("wait for " + waitTime + " milliseconds");
      await sleep(waitTime);
      await crawl(nextLinkObj);
    } else {
      setRootNode();
      printTree();
    }
  } catch (err) {
    console.log("Something Went Wrong...", err);
  }
}

function setRootNode() {
  while (currentNode.parent != null) {
    currentNode = currentNode.parent;
  }
  rootNode = currentNode;
}

function printTree() {
  addToPrintDFS(rootNode);
  console.log(printList.join("\n|"));
}

function addToPrintDFS(node) {
  let spaces = Array(node.depth * 3).join("-");
  printList.push(spaces + node.url);
  if (node.children) {
    node.children.map(function (i, x) {
      {
        addToPrintDFS(i);
      }
    });
  }
}

function checkDomain(linkURL) {
  let parsedUrl;
  let fullUrl = true;
  try {
    parsedUrl = new URL(linkURL);
  } catch (error) {
    fullUrl = false;
  }
  if (fullUrl === false) {
    if (linkURL.indexOf("/") === 0) {
      return mainParsedUrl.protocol + "//" + mainParsedUrl.hostname + linkURL.split("#")[0];
    } else if (linkURL.indexOf("#") === 0) {
      return;
    } else {
      let path = currentNode.url.match('.*\/')[0];
      return path + linkURL;
    }
  }
  let mainHostDomain = parsedUrl.hostname;
  if (mainDomain == mainHostDomain) {
    parsedUrl.hash = "";
    return parsedUrl.href;
  } else {
    return;
  }
}

function addToLinkQueue(linkobj) {
  if (!linkInSeenListExists(linkobj)) {
    if (linkobj.parent != null) {
      linkobj.parent.children.push(linkobj);
    }
    linksQueue.push(linkobj);
    addToSeen(linkobj);
  }
}

function getNextInQueue() {
  let nextLink = linksQueue.shift();
  if (nextLink && nextLink.depth > previousDepth) {
    previousDepth = nextLink.depth;
    console.log(`------- CRAWLING ON DEPTH LEVEL ${previousDepth} --------`);
  }
  return nextLink;
}

function peekInQueue() {
  return linksQueue[0];
}

function addToSeen(linkObj) {
  seenLinks[linkObj.url] = linkObj;
}

function linkInSeenListExists(linkObj) {
  return seenLinks[linkObj.url] == null ? false : true;
}

Проверьте наш API Store ⬇️https://www.scraping-bot.io/web-scraping-api-store/