AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / ubuntu / 问题 / 991447
Accepted
pa4080
pa4080
Asked: 2018-01-03 01:27:27 +0800 CST2018-01-03 01:27:27 +0800 CST 2018-01-03 01:27:27 +0800 CST

如何创建使用关键字和过滤内容的 CLI Web Spider?

  • 772

我想在已弃用(过时)的文学论坛e-bane.net中找到我的文章。一些论坛模块被禁用,我无法获得作者的文章列表。此外,该网站没有被 Google、Yndex 等搜索引擎收录。

找到我所有文章的唯一方法是打开网站的存档页面(图 1)。然后我必须选择特定的年份和月份 - 例如2013 年1 月(图 1)。然后我必须检查每篇文章(图2)是否在开头写了我的昵称-pa4080(图3)。但是有几千篇文章。

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

我已经阅读了以下几个主题,但没有一个解决方案适合我的需求:

  • Ubuntu 的网络蜘蛛
  • 如何在 Linux 系统上编写 Web 蜘蛛
  • 从站点获取 URL 列表

我将发布我自己的解决方案。但对我来说很有趣: 有没有更优雅的方法来解决这个任务?

command-line
  • 3 3 个回答
  • 1630 Views

3 个回答

  • Voted
  1. pa4080
    2018-01-03T01:28:05+08:002018-01-03T01:28:05+08:00

    为了解决这个任务,我创建了下一个主要使用 CLI 工具的简单bash 脚本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()使用wgetas --spider(这意味着它只会检查页面是否存在)并将创建具有深度级别的递归-rURL 。(可以在此处找到另一个示例:将网站转换为 PDF)。在当前情况下,它包含大约 20 000 个 URL。$MAP_FILE$TARGET_URL-l2$MAP_FILE

    • 第二个功能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
  2. Best Answer
    dan
    2018-01-03T20:00:31+08:002018-01-03T20:00:31+08:00

    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 版本(在 Ubuntu 17.10上的 python3.5 上测试)。

    如何使用:

    • 要使用它,请将两个代码都放在文件中。例如,代码文件是script.py,包文件是requirement.txt.
    • 运行pip install -r requirement.txt。
    • 运行脚本作为示例python3 script.py pa4080

    它使用了几个库:

    • 点击参数解析器
    • 用于 html 解析器的beautifulsoup
    • 用于 html 下载器的aiohttp

    进一步开发程序需要知道的事情(除了所需包的文档):

    • python 库:asyncio、json 和 urllib.parse
    • css 选择器(mdn web docs),还有一些 html。另请参阅如何在浏览器上使用 css 选择器,例如本文

    这个怎么运作:

    • 首先,我创建了一个简单的 html 下载器。它是 aiohttp 文档中给出的示例的修改版本。
    • 之后创建接受用户名和输出文件名的简单命令行解析器。
    • 为线程链接和主要文章创建解析器。使用 pdb 和简单的 url 操作应该可以完成这项工作。
    • 合并函数,把主文放在json上,方便后面其他程序处理。

    一些想法,因此可以进一步发展

    • 创建另一个接受日期模块链接的子命令:可以通过将解析日期模块的方法分离为自己的函数并将其与新的子命令组合来完成。
    • 缓存日期模块链接:获取线程链接后创建缓存json文件。所以程序不必再次解析链接。甚至只是缓存整个线程主文章,即使它不匹配

    这不是最优雅的答案,但我认为它比使用 bash 答案更好。

    • 它使用 Python,这意味着它可以跨平台使用。
    • 安装简单,所有需要的包都可以使用pip安装
    • 它可以进一步开发,程序更具可读性,更容易开发。
    • 它只在13 分钟内完成与bash 脚本相同的工作。
    • 3
  3. pa4080
    2018-04-07T03:28:49+08:002018-04-07T03:28:49+08:00

    我根据@karel提供的答案重新创建了我的脚本。现在脚本使用而不是. 结果它变得明显更快。lynxwget

    当前版本在有两个搜索关键字时执行相同的工作 15 分钟,如果我们只搜索一个关键字,则只需8 分钟。这比@dan提供的Python解决方案要快。

    此外,还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

相关问题

  • 如何从命令行仅安装安全更新?关于如何管理更新的一些提示

  • 如何从命令行刻录双层 dvd iso

  • 如何从命令行判断机器是否需要重新启动?

  • 文件权限如何工作?文件权限用户和组

  • 如何在 Vim 中启用全彩支持?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何运行 .sh 脚本?

    • 16 个回答
  • Marko Smith

    如何安装 .tar.gz(或 .tar.bz2)文件?

    • 14 个回答
  • Marko Smith

    如何列出所有已安装的软件包

    • 24 个回答
  • Marko Smith

    无法锁定管理目录 (/var/lib/dpkg/) 是另一个进程在使用它吗?

    • 25 个回答
  • Martin Hope
    Flimm 如何在没有 sudo 的情况下使用 docker? 2014-06-07 00:17:43 +0800 CST
  • Martin Hope
    Ivan 如何列出所有已安装的软件包 2010-12-17 18:08:49 +0800 CST
  • Martin Hope
    La Ode Adam Saputra 无法锁定管理目录 (/var/lib/dpkg/) 是另一个进程在使用它吗? 2010-11-30 18:12:48 +0800 CST
  • Martin Hope
    David Barry 如何从命令行确定目录(文件夹)的总大小? 2010-08-06 10:20:23 +0800 CST
  • Martin Hope
    jfoucher “以下软件包已被保留:”为什么以及如何解决? 2010-08-01 13:59:22 +0800 CST
  • Martin Hope
    David Ashford 如何删除 PPA? 2010-07-30 01:09:42 +0800 CST

热门标签

10.10 10.04 gnome networking server command-line package-management software-recommendation sound xorg

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve