Bagaimana cara membuat Spider Web CLI yang menggunakan kata kunci dan memfilter konten?

10

Saya ingin menemukan artikel saya di forum literatur yang sudah usang (usang) e-bane.net . Beberapa modul forum dinonaktifkan, dan saya tidak bisa mendapatkan daftar artikel dari penulisnya. Juga situs ini tidak diindeks oleh mesin pencari seperti Google, Yndex, dll.

Satu-satunya cara untuk menemukan semua artikel saya adalah membuka halaman arsip situs (gbr.1). Maka saya harus memilih tahun dan bulan tertentu - misalnya Januari 2013 (gbr.1). Dan kemudian saya harus memeriksa setiap artikel (gbr.2) apakah pada mulanya dituliskan nama panggilan saya - pa4080 (gbr.3). Tetapi ada beberapa ribu artikel.

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Saya telah membaca beberapa topik sebagai berikut, tetapi tidak ada solusi yang sesuai dengan kebutuhan saya:

Saya akan memposting solusi saya sendiri . Tetapi bagi saya itu menarik: Apakah ada cara yang lebih elegan untuk menyelesaikan tugas ini?

pa4080
sumber

Jawaban:

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

Berikut ini adalah versi skrip python3 (diuji pada python3.5 di Ubuntu 17.10 ).

Cara Penggunaan:

  • Untuk menggunakannya memasukkan kedua kode dalam file. Sebagai contoh file kode script.pydan file paket requirement.txt.
  • Lari pip install -r requirement.txt.
  • Jalankan skrip sebagai contoh python3 script.py pa4080

Ini menggunakan beberapa perpustakaan:

Hal-hal yang perlu diketahui untuk mengembangkan program lebih lanjut (selain dokumen paket yang diperlukan):

  • pustaka python: asyncio, json dan urllib.parse
  • penyeleksi css ( mdn web docs ), juga beberapa html. lihat juga cara menggunakan pemilih css di browser Anda seperti artikel ini

Bagaimana itu bekerja:

  • Pertama saya membuat pengunduh html sederhana. Ini adalah versi modifikasi dari sampel yang diberikan pada aiohttp doc.
  • Setelah itu membuat parser baris perintah sederhana yang menerima nama pengguna dan nama file keluaran.
  • Buat parser untuk tautan utas dan artikel utama. Menggunakan pdb dan manipulasi url sederhana harus melakukan pekerjaan.
  • Gabungkan fungsi dan letakkan artikel utama di json, sehingga program lain dapat memprosesnya nanti.

Ada ide sehingga bisa dikembangkan lebih lanjut

  • Buat subperintah lain yang menerima tautan modul tanggal: itu dapat dilakukan dengan memisahkan metode untuk menguraikan modul tanggal ke fungsinya sendiri dan menggabungkannya dengan subperintah baru.
  • Caching tautan modul tanggal: buat file cache json setelah mendapatkan tautan utas. jadi program tidak perlu menguraikan tautan lagi. atau bahkan hanya men-cache seluruh artikel utama utas meskipun tidak cocok

Ini bukan jawaban yang paling elegan, tapi saya pikir ini lebih baik daripada menggunakan jawaban bash.

  • Ini menggunakan Python, yang berarti dapat digunakan lintas platform.
  • Instalasi sederhana, semua paket yang diperlukan dapat diinstal menggunakan pip
  • Ini dapat dikembangkan lebih lanjut, lebih mudah dibaca programnya, lebih mudah dikembangkan.
  • Ia melakukan pekerjaan yang sama dengan skrip bash hanya selama 13 menit .
dan
sumber
Oke, saya sudah berhasil menginstal beberapa modul:, sudo apt install python3-bs4 python3-click python3-aiohttp python3-asynctetapi saya tidak dapat menemukan - dari mana paket itu async_timeoutberasal?
pa4080
@ pa4080 saya instal dengan pip jadi harus disertakan dengan aiohttp. bagian dari 2 fungsi pertama dimodifikasi dari sini aiohttp.readthedocs.io/en/stable . Saya juga akan menambahkan instruksi untuk menginstal paket yang diperlukan
dan
Saya berhasil menginstal modul menggunakan pip. Tetapi beberapa kesalahan lain muncul: paste.ubuntu.com/26311694 . Tolong ping saya ketika Anda melakukan itu :)
pa4080
@ pa4080, saya tidak bisa meniru kesalahan Anda, jadi saya sederhanakan fungsi ambil. efek sampingnya adalah bahwa program dapat melempar kesalahan jika coba lagi tidak berfungsi
dan
1
Kontra utama adalah bahwa saya telah berhasil menjalankan skrip hanya di Ubuntu 17.10. Namun ini 5 kali lebih cepat dari skrip bash saya, jadi saya memutuskan untuk menerima jawaban ini.
pa4080
10

Untuk menyelesaikan tugas ini, saya telah membuat skrip bash sederhana berikutnya yang terutama menggunakan alat 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

Script memiliki tiga fungsi:

  • Fungsi pertama get_url_map()penggunaan wgetsebagai --spider(yang berarti bahwa itu hanya akan memeriksa bahwa halaman ada) dan akan membuat rekursif -rURL $MAP_FILEdari $TARGET_URLdengan tingkat kedalaman -l2. (Contoh lain dapat ditemukan di sini: Konversi Situs Web ke PDF ). Dalam kasus saat ini $MAP_FILEberisi sekitar 20 000 URL.

  • Fungsi kedua filter_url_map()akan menyederhanakan konten $MAP_FILE. Dalam hal ini kita hanya perlu baris (URL) yang berisi string article&siddan jumlahnya sekitar 3000. Lebih banyak ide dapat ditemukan di sini: Bagaimana menghapus kata-kata tertentu dari baris file teks?

  • Fungsi ketiga get_key_urls()akan menggunakan wget -qO-(sebagai perintah curl- contoh ) untuk menampilkan konten setiap URL dari $MAP_FILEdan akan mencoba untuk menemukan salah satu $KEY_WORDSdi dalamnya. Jika ada yang $KEY_WORDSditemukan di dalam konten URL tertentu, URL itu akan disimpan dalam $OUT_FILE.

Selama proses kerja, output skrip terlihat seperti yang ditunjukkan pada gambar berikutnya. Diperlukan sekitar 63 menit untuk menyelesaikannya jika ada dua kata kunci dan 42 menit ketika hanya satu kata kunci yang dicari.

masukkan deskripsi gambar di sini

pa4080
sumber
1

Saya membuat ulang skrip saya berdasarkan jawaban yang diberikan oleh @karel ini . Sekarang skrip menggunakan lynxalih-alih wget. Hasilnya menjadi lebih cepat secara signifikan.

Versi saat ini melakukan pekerjaan yang sama selama 15 menit ketika ada dua kata kunci yang dicari dan hanya 8 menit jika kita mencari hanya satu kata kunci. Itu lebih cepat daripada solusi Python yang disediakan oleh @dan .

Selain itu lynxmemberikan penanganan karakter non latin yang lebih baik.

#!/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
pa4080
sumber