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

FWIW, мне больше нравится то, что я получаю из бок о бок, вывод из diff

diff -y -W 120 File_1.txt File_2.txt

даст что-то вроде:

User1 US                            User1 US
User2 US                            User2 US
User3 US                          | User3 NG
10
задан 9 January 2018 в 11:42

5 ответов

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). [ ! d2]

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

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

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

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

Что нужно знать для разработки далее (кроме документа требуемого пакета):

beautifulsoup для селекторов html css ( beautifulsoup ), а также некоторые html. см. также, как использовать css-селектор в вашем браузере, например эту статью

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

css селектора ( beautifulsoup ), также некоторые HTML. см. также, как использовать селектор CSS в вашем браузере, такой как эта статья . После этого создается простой синтаксический анализатор командной строки, который принимает имя пользователя и выводит имя файла. Запустите сценарий в качестве примера python3 script.py pa4080 Объедините функцию и поместите основную статью в json, чтобы другая программа могла ее обработать позже.

Некоторая идея, поэтому ее можно продолжить дальше

После этого создается простой синтаксический анализатор командной строки, который принимает имя пользователя и выводит имя файла. Кэширование ссылки модуля даты: create cache json file после получения ссылки на нить. поэтому программе не нужно снова разбирать ссылку. или даже просто кэшировать всю статью основного потока, даже если она не соответствует

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

Кэширование ссылки модуля даты: создайте файл кеша json после получения ссылки на нить. поэтому программе не нужно снова разбирать ссылку. или даже просто кэшировать всю основную статью потока, даже если она не соответствует Объедините функцию и поместите основную статью на json, чтобы другая программа могла ее обработать позже. Может быть разработано далее, более читаемая программа, проще ее можно разработать. Он выполняет ту же работу, что и скрипт bash всего за 13 минут.
3
ответ дан 22 May 2018 в 15:48
  • 1
    Хорошо, мне удалось установить некоторые модули: sudo apt install python3-bs4 python3-click python3-aiohttp python3-async, но я не могу найти - из какого пакета async_timeout? – pa4080 3 January 2018 в 12:21
  • 2
    @ pa4080 i install with pip, поэтому он должен быть включен в aiohttp. части первой функции 2 здесь изменены aiohttp.readthedocs.io/en/stable . также я добавлю инструкцию по установке требуемого пакета – dan 3 January 2018 в 12:37
  • 3
    Я успешно установил модуль с помощью pip. Но появляется и другая ошибка: paste.ubuntu.com/26311694 . Пожалуйста, пиньте мне, когда вы это сделаете :) – pa4080 3 January 2018 в 12:41
  • 4
    @ pa4080, я не могу реплицировать вашу ошибку, поэтому я упрощаю функцию выборки. побочный эффект заключается в том, что программа может выдать ошибку, если вторая попытка не работает – dan 3 January 2018 в 12:57
  • 5
    Главным недостатком является то, что мне удалось успешно запустить скрипт только на Ubuntu 17.10. Однако он в 5 раз быстрее, чем мой сценарий bash, поэтому я решил принять этот ответ. – pa4080 8 January 2018 в 12:57

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

Текущая версия выполняет ту же работу в течение 15 минут, если есть два искомых ключевых слова и только lynx , если мы ищем только одно ключевое слово. Это быстрее, чем решение my script , предоставленное @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 'wget' as spider and output the result into a file (and stdout) 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
3
ответ дан 24 July 2018 в 17:09

Чтобы решить эту задачу, я создал следующий простой сценарий 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\)$' | 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 - примеры) для вывода содержимого каждого URL-адреса из $MAP_FILE и попытается найти любой из $KEY_WORDS внутри него. Если какой-либо из $KEY_WORDS основан на содержимом любого конкретного URL-адреса, этот URL-адрес будет сохранен в $OUT_FILE.

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

10
ответ дан 22 May 2018 в 15:48

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

Текущая версия выполняет ту же работу в течение 15 минут, если есть два искомых ключевых слова и только lynx , если мы ищем только одно ключевое слово. Это быстрее, чем решение my script , предоставленное @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 'wget' as spider and output the result into a file (and stdout) 
    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
ответ дан 22 May 2018 в 15:48

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

Текущая версия выполняет ту же работу в течение 15 минут, если есть два искомых ключевых слова и только lynx , если мы ищем только одно ключевое слово. Это быстрее, чем решение my script , предоставленное @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 'wget' as spider and output the result into a file (and stdout) 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
ответ дан 18 July 2018 в 00:03

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

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