Задача: очистить веб-сайт Hacker News и вывести выбранное количество сообщений в консоль с помощью команды hackernews -p [num of posts]
.
Во время разработки я пробовал несколько разных библиотек, и мое последнее решение написано с использованием:
node-fetch
для API получения,cheerio
для выбора элементов HTML,commander
за помощь в запуске программы из командной строки и разрешение настраиваемых командных флагов, таких как:hackernews -p 5
иhackernews --help
.
Быстрая настройка: mkdir <name>
, cd <name>
, запустите npm init -y
(вопросы пропускаются), npm i
(устанавливает модули узлов), затем создайте server.js
файл, в который мы будем писать наш код.
Также создайте файл .gitignore
со следующим:
# dependencies /node_modules
Этот код будет игнорировать модули узлов при отправке на GitHub.
ПЛАН:
Давайте разберемся с нашим подходом. Что мы хотим делать и в каком порядке.
- Очистите веб-сайт. Получите необработанный HTML. Мы получим один объединенный html для всех нужных страниц в зависимости от количества требуемых постов.
- Извлеките нужные нам значения из этого HTML.
Cheerio
выполнит здесь работу. - Подтвердите значения. Нам нужно убедиться, что значение комментариев - это число, действительный uri и т. Д.
- Сделайте это через интерфейс командной строки. Добавьте
hackernews -p [num of posts]
команду для запуска из консоли. Что делает его CLI (интерфейс командной строки). Здесь мы будем использоватьcommander
и добавим несколько вещей вpackage.json
.
Давайте рассмотрим это.
ШАГ 1. Очистите веб-сайт.
Добавьте зависимости:
npm i node-fetch npm i cheerio
Затем в server.js
введите:
const fetch = require('node-fetch') const cheerio = require('cheerio') const getPagesArray = (numberOfPosts) => Array(Math.ceil(numberOfPosts / 30)) //divides by 30 (posts per page) .fill() //creates a new array .map((_, index) => index + 1) //[1, 2, 3, 4,..] PagesArray const getPageHTML = (pageNumber) => fetch(`https://news.ycombinator.com/news?p=${pageNumber}`) .then(resp => resp.text()) //Promise const getAllHTML = async (numberOfPosts) => { return Promise.all(getPagesArray(numberOfPosts).map(getPageHTML)) .then(htmls => console.log(htmls.join(''))) //one JOINED html } getAllHTML(5) // get all HTML for 5 posts
… И запустите node server
в консоли, чтобы увидеть вывод html для 5 сообщений.
В функции getPagesArray
мы вычисляем, сколько страниц нам нужно будет извлечь, чтобы получить html, необходимый для numberOfPosts
необходимого. Если бы мы хотели 125 сообщений, Math.ceil(125 / 30) = 5
, а затем:
… Затем [1,2,3,4,5].map(getPageHTML)
Шаг 1 завершен. У нас есть необработанный HTML.
ШАГ 2: Извлеките нужные нам значения.
Теперь мы воспользуемся программой cheerio, чтобы получить title
, uri
, author
, points
, comments
и rank
публикации из имеющегося у нас HTML. И мы поместим объект сообщения в массив results
только на необходимое нам количество сообщений.
const getPosts = (html, posts) => { let results = [] let $ = cheerio.load(html) $('span.comhead').each(function() { let a = $(this).prev() let title = a.text() let uri = a.attr('href') let rank = a.parent().parent().text() let subtext = a.parent().parent().next().children('.subtext').children() let author = $(subtext).eq(1).text() let points = $(subtext).eq(0).text() let comments = $(subtext).eq(5).text() let obj = { title: title, uri: uri, author: author, points: points, comments: comments, rank: parseInt(rank) } if (obj.rank <= posts) { results.push(obj) } }) if (results.length > 0) { console.log(results) return results } }
Измените функцию getAllHTML
для вызова функции getPosts
в конце:
const getAllHTML = async (numberOfPosts) => { return Promise.all(getPagesArray(numberOfPosts).map(getPageHTML)) .then(htmls => getPosts(htmls.join(''), numberOfPosts)) } getAllHTML(5)
… И запустите node server
в консоли, чтобы увидеть вывод html из 5 сообщений в этом формате:
Шаг 2 завершен. Теперь у нас есть массив объектов с нужными нам данными.
ШАГ 3: Проверьте значения.
Теперь мы создадим несколько вспомогательных функций, в которых мы будем проверять наши значения, прежде чем они попадут в наш объект.
//VALIDATIONS: const checkInput = (input) => { if (input.length > 0 && input.length < 256){ return input }else { return input.substring(0,25)+"..." } } const checkURI = (uri) => { let regex = /(^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)/ if (regex.test(uri)){ return uri }else { return "uri not valid" } } const checkPoints = (points) => { if (parseInt(points) <= 0) { return 0 }else { return parseInt(points) } } const checkComments = (comments) => { if (comments === 'discuss' || comments === '' || parseInt(comments) <= 0) { return 0 }else { return parseInt(comments) } }
Измените getPosts
, func для вызова функций проверки при формировании obj
:
let obj = { title: checkInput(title), uri: checkURI(uri), author: checkInput(author), points: checkPoints(points), comments: checkComments(comments), rank: parseInt(rank) }
Шаг 3 завершен. Мы добавили функции проверки.
ШАГ 4. Давайте сделаем это через интерфейс командной строки.
То, что у нас есть сейчас, великолепно, мы вызываем getAllHTML(25)
и получаем 25 сообщений в желаемом формате. Но мы хотим иметь возможность вызывать hackernews -p 25
из командной строки, чтобы получить эти 25 сообщений. Здесь нам поможет commander
.
Добавьте зависимость с npm i commander
. И потребовать это в server.js
:
const program = require('commander')
Напишите эту «командирскую» функцию, которая будет вызывать наши getAllHTML
и getPosts
функции. Здесь мы указываем флаги, такие как -p
и--posts
. 30 - наше значение по умолчанию.
program .option('-p, --posts [value]', 'Number of posts', 30) .action(args => getAllHTML(args.posts) .then(html => getPosts(html, args.posts)) ) program.parse(process.argv)
А затем измените функцию getAllHTML
, чтобы она возвращала только html:
const getAllHTML = async (numberOfPosts) => { return Promise.all(getPagesArray(numberOfPosts).map(getPageHTML)) .then(htmls => htmls.join('')) }
И последнее, что мы сделаем в нашем server.js
файле, - это добавим эту строку shebang в стиле Unix вверху:
#!/usr/bin/env node
Вот некоторая информация об этом, но она предназначена для того, чтобы разрешить символическую ссылку на этот файл с командой, которую мы хотим выполнить из командной строки. Если бы у нас не было этой строки и мы сделали бы все, что указано ниже в этой статье, мы бы получили синтаксическую ошибку.
Теперь мы могли позвонить node server -p 10
, чтобы получить 10 сообщений. Но мы хотим использовать hackernews
или любое другое слово вместо node server
. Итак, мы переходим к:
package.json
… Файл, в котором мы можем изменить несколько вещей, чтобы это произошло.
- Мы предоставим поле
bin
в нашемpackage.json
, которое является отображением имени команды и имени локального файла.hackernews
- это команда, которую я выбрал для вызова вместоnode server
, а./server.js
- это мой локальный файл сценария, который будет запускаться с этой командой. Этот формат позволяет нам при необходимости предоставлять более одного сопоставления скриптов.
"dependencies": { "cheerio": "^1.0.0-rc.2", "commander": "^2.18.0", "node-fetch": "^2.2.0" }, "bin": { "hackernews": "./server.js" } }
Затем запустите:
npm link
А теперь запустите hackernews -p 17
или hackernews --posts 17
или любое другое количество сообщений, которое вы хотите получить, и убедитесь, что это работает.
УДИВИТЕЛЬНЫЙ.
Вы всегда можете npm unlink
и изменить название команды.
2. Теперь давайте настроим команду, которую будут запускать другие люди (которые клонировали ваше репо), чтобы все это нормально работало. Добавьте эту жирную строку там, где это указано:
"scripts": { "install-hackernews": "npm link && npm i -g", "test": "echo \"Error: no test specified\" && exit 1" },
Им нужно будет запустить:
npm run install-hackernews
… Чтобы запустить все остальные команды (глобальная установка link
и npm i -g
, чтобы hackernews -p 25
можно было запускать из любого другого каталога, например из рабочего стола). Здесь вы можете выбрать любое имя:
"<chosen-name>": "npm link && npm i -g",
Команда npm link
позволяет нам локально «создать символическую ссылку на папку пакета», она локально установит любую команду, указанную в поле bin
нашего package.json
. Другими словами, npm link
здесь похож на симулятор установки пакета NodeJS.
Шаг 4 завершен. Здесь у нас есть интерфейс командной строки.
Быстрый эксперимент:
Давайте быстро проверим, как все это работает, если мы создали новый файл с именем day.js
и в нем написали только:
#!/usr/bin/env node const days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'] const now = new Date(); console.log("Today is " + days[now.getDay()] + String.fromCodePoint(0x1f43c))
Затем в package.json:
"bin": { "hackernews": "./server.js", "day": "./day.js" }
и запускаем: npm link
, а затем day
. Что ты видишь? Что бы вы увидели, если вернетесь на рабочий стол и запустите day
?
Спасибо за кодирование! Вот ссылка на мое репо: