Làm cách nào để tạo một Spider Web CLI sử dụng từ khóa và lọc nội dung?


10

Tôi muốn tìm các bài viết của mình trong diễn đàn văn học (đã lỗi thời) e-bane.net . Một số mô-đun diễn đàn bị vô hiệu hóa và tôi không thể lấy danh sách các bài viết của tác giả. Ngoài ra, trang web không được lập chỉ mục bởi các công cụ tìm kiếm như Google, Yndex, v.v.

Cách duy nhất để tìm tất cả các bài viết của tôi là mở trang lưu trữ của trang web (hình 1). Sau đó, tôi phải chọn một số năm và tháng nhất định - ví dụ: tháng 1 năm 2013 (hình 1). Và sau đó tôi phải kiểm tra từng bài viết (hình 2) xem ban đầu có viết biệt danh của tôi - pa4080 (hình 3) không. Nhưng có vài ngàn bài báo.

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Tôi đã đọc một vài chủ đề như sau, nhưng không có giải pháp nào phù hợp với nhu cầu của tôi:

Tôi sẽ đăng giải pháp của riêng tôi . Nhưng đối với tôi thật thú vị: Có cách nào thanh lịch hơn để giải quyết nhiệm vụ này không?

Câu trả lời:


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

Đây là phiên bản python3 của tập lệnh (được thử nghiệm trên python3.5 trên Ubuntu 17.10 ).

Cách sử dụng:

  • Để sử dụng nó đặt cả hai mã trong các tập tin. Ví dụ tập tin mã là script.pyvà tập tin gói là requirement.txt.
  • Chạy đi pip install -r requirement.txt.
  • Chạy script như ví dụ python3 script.py pa4080

Nó sử dụng một số thư viện:

Những điều cần biết để phát triển chương trình hơn nữa (ngoài tài liệu của gói yêu cầu):

  • thư viện python: asyncio, json và urllib.parse
  • bộ chọn css ( tài liệu web mdn ), cũng có một số html. xem thêm cách sử dụng bộ chọn css trên trình duyệt của bạn, chẳng hạn như bài viết này

Làm thế nào nó hoạt động:

  • Đầu tiên tôi tạo một trình tải xuống html đơn giản. Nó là phiên bản sửa đổi từ mẫu được đưa ra trên tài liệu aiohttp.
  • Sau đó, tạo trình phân tích cú pháp dòng lệnh đơn giản chấp nhận tên người dùng và tên tệp đầu ra.
  • Tạo một trình phân tích cú pháp cho các liên kết chủ đề và bài viết chính. Sử dụng pdb và thao tác url đơn giản sẽ thực hiện công việc.
  • Kết hợp chức năng và đặt bài viết chính trên json, để chương trình khác có thể xử lý nó sau.

Một số ý tưởng để nó có thể được phát triển thêm

  • Tạo một tiểu ban khác chấp nhận liên kết mô-đun ngày: có thể được thực hiện bằng cách tách phương thức để phân tích mô-đun ngày thành chức năng của chính nó và kết hợp nó với tiểu ban mới.
  • Bộ nhớ đệm liên kết mô-đun ngày: tạo tập tin bộ nhớ cache sau khi nhận được liên kết chủ đề. vì vậy chương trình không phải phân tích lại liên kết. hoặc thậm chí chỉ lưu trữ toàn bộ bài viết chính của chủ đề ngay cả khi nó không khớp

Đây không phải là câu trả lời tao nhã nhất, nhưng tôi nghĩ nó tốt hơn là sử dụng câu trả lời bash.

  • Nó sử dụng Python, có nghĩa là nó có thể được sử dụng đa nền tảng.
  • Cài đặt đơn giản, tất cả các gói cần thiết có thể được cài đặt bằng pip
  • Nó có thể được phát triển hơn nữa, chương trình dễ đọc hơn, dễ dàng phát triển hơn.
  • Nó thực hiện công việc tương tự như tập lệnh bash chỉ trong 13 phút .

Ok, tôi đã quản lý để cài đặt một số mô-đun: sudo apt install python3-bs4 python3-click python3-aiohttp python3-asyncnhưng tôi không thể tìm thấy - gói async_timeoutđến từ đâu?
pa4080

@ pa4080 tôi cài đặt với pip vì vậy nó nên đi kèm với aiohttp. các phần của hàm 2 đầu tiên được sửa đổi từ đây aiohttp.readthedocs.io/en/urdy . còn tôi sẽ bổ sung thêm hướng dẫn để cài đặt các gói cần thiết
dan

Tôi đã cài đặt thành công mô-đun bằng pip. Nhưng một số lỗi khác xuất hiện: paste.ubfox.com/26311694 . Hãy ping tôi khi bạn làm điều đó :)
pa4080

@ pa4080, tôi không thể sao chép lỗi của bạn, vì vậy tôi đơn giản hóa chức năng tìm nạp. tác dụng phụ là chương trình có thể ném lỗi nếu thử lại lần thứ hai không làm việc
dan

1
Nhược điểm chính là tôi đã quản lý để chạy thành công tập lệnh chỉ trên Ubuntu 17.10. Tuy nhiên, nó nhanh hơn 5 lần so với kịch bản bash của tôi, vì vậy tôi quyết định chấp nhận câu trả lời này.
pa4080

10

Để giải quyết nhiệm vụ này, tôi đã tạo tập lệnh bash đơn giản tiếp theo , chủ yếu sử dụng công cụ 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

Kịch bản có ba chức năng:

  • Chức năng đầu tiên get_url_map()sử dụng wgetnhư --spider(có nghĩa là nó sẽ chỉ cần kiểm tra rằng các trang đang có) và sẽ tạo ra đệ quy -rURL $MAP_FILEcủa $TARGET_URLvới mức độ sâu -l2. (Một ví dụ khác có thể được tìm thấy ở đây: Chuyển đổi trang web sang PDF ). Trong trường hợp hiện tại, nó $MAP_FILEchứa khoảng 20 000 URL.

  • Hàm thứ hai filter_url_map()sẽ đơn giản hóa nội dung của $MAP_FILE. Trong trường hợp này, chúng tôi chỉ cần các dòng (URL) có chứa chuỗi article&sidvà chúng có khoảng 3000. Có thể tìm thấy nhiều ý tưởng hơn ở đây: Làm cách nào để xóa các từ cụ thể khỏi các dòng của tệp văn bản?

  • Hàm thứ ba get_key_urls()sẽ sử dụng wget -qO-(như lệnh curl- ví dụ ) để xuất nội dung của từng URL từ $MAP_FILEvà sẽ cố gắng tìm bất kỳ nội dung nào $KEY_WORDStrong đó. Nếu bất kỳ trong số đó $KEY_WORDSđược thành lập trong nội dung của bất kỳ URL cụ thể, URL đó sẽ được lưu trong $OUT_FILE.

Trong quá trình làm việc, đầu ra của tập lệnh trông như được hiển thị trên hình ảnh tiếp theo. Sẽ mất khoảng 63 phút để hoàn thành nếu có hai từ khóa và 42 phút khi chỉ có một từ khóa được tìm kiếm.

nhập mô tả hình ảnh ở đây


1

Tôi đã tạo lại kịch bản của mình dựa trên câu trả lời này được cung cấp bởi @karel . Bây giờ kịch bản sử dụng lynxthay vì wget. Kết quả là nó trở nên nhanh hơn đáng kể.

Phiên bản hiện tại thực hiện cùng một công việc trong 15 phút khi có hai từ khóa được tìm kiếm và chỉ 8 phút nếu chúng tôi chỉ tìm kiếm một từ khóa. Đó là nhanh hơn giải pháp Python được cung cấp bởi @dan .

Ngoài ra lynxcung cấp xử lý tốt hơn của các nhân vật không Latin.

#!/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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.