Как создать CLI Web Spider, который использует ключевые слова и фильтрует контент?

Я хочу найти свои статьи на устаревшем (устаревшем) литературном форуме e-bane.net . Некоторые из модулей форума отключены, и я не могу получить список статей их авторов. Также сайт не индексируется поисковыми системами как Google, Yndex и т. Д.

Единственный способ найти все мои статьи - открыть страницу архива сайта (рис.1). Затем я должен выбрать определенный год и месяц - например, январь 2013 г. (рис. 1). И тогда я должен проверить каждую статью (рис.2), пишется ли в начале мой ник - pa4080 (рис.3). Но есть несколько тысяч статей.

enter image description here

enter image description here

enter image description here

Я прочитал несколько тем следующим образом, но ни одно из решений не соответствует моим потребностям:

Я опубликую мое собственное решение . Но для меня интересно: Есть ли более изящный способ решить эту задачу?

10
задан 9 January 2018 в 10:42

3 ответа

script.py:

#!/usr/bin/python3
from urllib.parse import urljoin
import json

import bs4
import click
import aiohttp
import asyncio
import async_timeout


BASE_URL = 'http://e-bane.net'


async def fetch(session, url):
    try:
        with async_timeout.timeout(20):
            async with session.get(url) as response:
                return await response.text()
    except asyncio.TimeoutError as e:
        print('[{}]{}'.format('timeout error', url))
        with async_timeout.timeout(20):
            async with session.get(url) as response:
                return await response.text()


async def get_result(user):
    target_url = 'http://e-bane.net/modules.php?name=Stories_Archive'
    res = []
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, target_url)
        html_soup = bs4.BeautifulSoup(html, 'html.parser')
        date_module_links = parse_date_module_links(html_soup)
        for dm_link in date_module_links:
            html = await fetch(session, dm_link)
            html_soup = bs4.BeautifulSoup(html, 'html.parser')
            thread_links = parse_thread_links(html_soup)
            print('[{}]{}'.format(len(thread_links), dm_link))
            for t_link in thread_links:
                thread_html = await fetch(session, t_link)
                t_html_soup = bs4.BeautifulSoup(thread_html, 'html.parser')
                if is_article_match(t_html_soup, user):
                    print('[v]{}'.format(t_link))
                    # to get main article, uncomment below code
                    # res.append(get_main_article(t_html_soup))
                    # code below is used to get thread link
                    res.append(t_link)
                else:
                    print('[x]{}'.format(t_link))

        return res


def parse_date_module_links(page):
    a_tags = page.select('ul li a')
    hrefs = a_tags = [x.get('href') for x in a_tags]
    return [urljoin(BASE_URL, x) for x in hrefs]


def parse_thread_links(page):
    a_tags = page.select('table table  tr  td > a')
    hrefs = a_tags = [x.get('href') for x in a_tags]
    # filter href with 'file=article'
    valid_hrefs = [x for x in hrefs if 'file=article' in x]
    return [urljoin(BASE_URL, x) for x in valid_hrefs]


def is_article_match(page, user):
    main_article = get_main_article(page)
    return main_article.text.startswith(user)


def get_main_article(page):
    td_tags = page.select('table table td.row1')
    td_tag = td_tags[4]
    return td_tag


@click.command()
@click.argument('user')
@click.option('--output-filename', default='out.json', help='Output filename.')
def main(user, output_filename):
    loop = asyncio.get_event_loop()
    res = loop.run_until_complete(get_result(user))
    # if you want to return main article, convert html soup into text
    # text_res = [x.text for x in res]
    # else just put res on text_res
    text_res = res
    with open(output_filename, 'w') as f:
        json.dump(text_res, f)


if __name__ == '__main__':
    main()

requirement.txt:

aiohttp>=2.3.7
beautifulsoup4>=4.6.0
click>=6.7

Здесь приведена версия скрипта на python3 (протестирована на python3.5 в Ubuntu 17.10 ]).

Как использовать:

  • Чтобы использовать его, поместите оба кода в файлы. Например, файл кода - script.py, а файл пакета - requirement.txt.
  • Выполнить pip install -r requirement.txt.
  • Запустить скрипт как пример. python3 script.py pa4080

Он использует несколько библиотек:

Что нужно знать для дальнейшего развития программы (кроме документации по запросу пакет):

  • библиотека Python: селекторы asyncio, json и urllib.parse
  • css ( веб-документы mdn ), также некоторые html. см. также, как использовать css-селектор в вашем браузере, например , эту статью

Как это работает:

  • Сначала я создаю простой загрузчик HTML. Это модифицированная версия из образца, приведенного в документе aiohttp.
  • После этого создается простой анализатор командной строки, который принимает имя пользователя и имя выходного файла.
  • Создать парсер ссылок на темы и основную статью. Использование pdb и простое манипулирование URL должны сделать эту работу.
  • Объедините функцию и поместите основную статью в json, чтобы другие программы могли обработать ее позже.

Некоторая идея, чтобы ее можно было развивать дальше.

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

Это не самый элегантный ответ, но я думаю, что это лучше, чем использование ответа bash.

  • Он использует Python, что означает, что он может быть использован кроссплатформенным.
  • Простая установка, все необходимые пакеты могут быть установлены с помощью pip.
  • Она может быть доработана, удобнее для чтения, проще в разработке.
  • Он выполняет ту же работу, что и скрипт bash , только в течение 13 минут .
3
ответ дан 9 January 2018 в 10:42

Я воссоздал свой сценарий на основе этого ответа , предоставленного @karel . Теперь сценарий использует lynx вместо wget . В результате это становится значительно быстрее.

Текущая версия выполняет ту же работу в течение 15 минут, когда есть два искомых ключевых слова, и только 8 минут , если мы ищем только одно ключевое слово. Это быстрее, чем решение Python , предоставленное @dan .

Кроме того, lynx обеспечивает лучшую обработку нелатинских символов.

#!/bin/bash

TARGET_URL='http://e-bane.net/modules.php?name=Stories_Archive'
KEY_WORDS=('pa4080')  # KEY_WORDS=('word' 'some short sentence')
MAP_FILE='url.map'
OUT_FILE='url.list'

get_url_map() {
    # Use 'lynx' as spider and output the result into a file 
    lynx -dump "${TARGET_URL}" | awk '/http/{print $2}' | uniq -u > "$MAP_FILE"
    while IFS= read -r target_url; do lynx -dump "${target_url}" | awk '/http/{print $2}' | uniq -u >> "${MAP_FILE}.full"; done < "$MAP_FILE"
    mv "${MAP_FILE}.full" "$MAP_FILE"
}

filter_url_map() {
    # Apply some filters to the $MAP_FILE and keep only the URLs, that contain 'article&sid'
    uniq "$MAP_FILE" | grep -v '\.\(css\|js\|png\|gif\|jpg\|txt\) | grep 'article&sid' | sort -u > "${MAP_FILE}.uniq"
    mv "${MAP_FILE}.uniq" "$MAP_FILE"
    printf '\n# -----\nThe number of the pages to be scanned: %s\n' "$(cat "$MAP_FILE" | wc -l)"
}

get_key_urls() {
    counter=1
    # Do this for each line in the $MAP_FILE
    while IFS= read -r URL; do
        # For each $KEY_WORD in $KEY_WORDS
        for KEY_WORD in "${KEY_WORDS[@]}"; do
            # Check if the $KEY_WORD exists within the content of the page, if it is true echo the particular $URL into the $OUT_FILE
            if [[ ! -z "$(lynx -dump -nolist "${URL}" | grep -io "${KEY_WORD}" | head -n1)" ]]; then
                echo "${URL}" | tee -a "$OUT_FILE"
                printf '%s\t%s\n' "${KEY_WORD}" "YES"
            fi
        done
        printf 'Progress: %s\r' "$counter"; ((counter++))
    done < "$MAP_FILE"
}

# Call the functions
get_url_map
filter_url_map
get_key_urls
1
ответ дан 9 January 2018 в 10:42

Для решения этой задачи я создал следующий простой bash-скрипт, который в основном использует инструмент CLI wget.

#!/bin/bash

TARGET_URL='http://e-bane.net/modules.php?name=Stories_Archive'
KEY_WORDS=('pa4080' 's0ther')
MAP_FILE='url.map'
OUT_FILE='url.list'

get_url_map() {
    # Use 'wget' as spider and output the result into a file (and stdout) 
    wget --spider --force-html -r -l2 "${TARGET_URL}" 2>&1 | grep '^--' | awk '{ print $3 }' | tee -a "$MAP_FILE"
}

filter_url_map() {
    # Apply some filters to the $MAP_FILE and keep only the URLs, that contain 'article&sid'
    uniq "$MAP_FILE" | grep -v '\.\(css\|js\|png\|gif\|jpg\|txt\) 

Скрипт имеет три функции:

  • Первая функция get_url_map() использует wget как --spider (что означает, что он просто проверяет наличие страниц). ) и создаст рекурсивный -r URL $MAP_FILE из $TARGET_URL с уровнем глубины -l2. (Другой пример можно найти здесь: Конвертировать сайт в PDF ). В текущем случае $MAP_FILE содержит около 20 000 URL-адресов.

  • Вторая функция filter_url_map() упростит содержание $MAP_FILE. В этом случае нам нужны только строки (URL), которые содержат строку article&sid, а их около 3000. Больше идей можно найти здесь: Как удалить определенные слова из строк текстового файла?

  • Третья функция get_key_urls() будет использовать wget -qO- (как команда curl - examples ) для вывода содержимого каждого URL из $MAP_FILE и постараюсь найти любой из $KEY_WORDS внутри него. Если какой-либо из $KEY_WORDS найден в содержимом какого-либо конкретного URL, этот URL будет сохранен в $OUT_FILE.

В процессе работы вывод скрипта выглядит так, как показано на следующем изображении. Для завершения требуется около 63 минут, если есть два ключевых слова, и 42 минуты , когда выполняется поиск только по одному ключевому слову.

enter image description here

| grep 'article&sid' | sort -u > "${MAP_FILE}.uniq" mv "${MAP_FILE}.uniq" "$MAP_FILE" printf '\n# -----\nThe number of the pages to be scanned: %s\n' "$(cat "$MAP_FILE" | wc -l)" } get_key_urls() { counter=1 # Do this for each line in the $MAP_FILE while IFS= read -r URL; do # For each $KEY_WORD in $KEY_WORDS for KEY_WORD in "${KEY_WORDS[@]}"; do # Check if the $KEY_WORD exists within the content of the page, if it is true echo the particular $URL into the $OUT_FILE if [[ ! -z "$(wget -qO- "${URL}" | grep -io "${KEY_WORD}" | head -n1)" ]]; then echo "${URL}" | tee -a "$OUT_FILE" printf '%s\t%s\n' "${KEY_WORD}" "YES" fi done printf 'Progress: %s\r' "$counter"; ((counter++)) done < "$MAP_FILE" } # Call the functions get_url_map filter_url_map get_key_urls

Скрипт имеет три функции:

  • Первая функция get_url_map() использует wget как --spider (что означает, что он просто проверяет наличие страниц). ) и создаст рекурсивный -r URL $MAP_FILE из $TARGET_URL с уровнем глубины -l2. (Другой пример можно найти здесь: Конвертировать сайт в PDF ). В текущем случае $MAP_FILE содержит около 20 000 URL-адресов.

  • Вторая функция filter_url_map() упростит содержание $MAP_FILE. В этом случае нам нужны только строки (URL), которые содержат строку article&sid, а их около 3000. Больше идей можно найти здесь: Как удалить определенные слова из строк текстового файла?

  • Третья функция get_key_urls() будет использовать wget -qO- (как команда curl - examples ) для вывода содержимого каждого URL из $MAP_FILE и постараюсь найти любой из $KEY_WORDS внутри него. Если какой-либо из $KEY_WORDS найден в содержимом какого-либо конкретного URL, этот URL будет сохранен в $OUT_FILE.

В процессе работы вывод скрипта выглядит так, как показано на следующем изображении. Для завершения требуется около 63 минут, если есть два ключевых слова, и 42 минуты , когда выполняется поиск только по одному ключевому слову.

enter image description here

10
ответ дан 9 January 2018 в 10:42

Другие вопросы по тегам:

Похожие вопросы: