Python爬取StackOverflow问题页面并转换为WordPress可用格式(2022/4/5更新)

博客:https://mwhls.top/2175.html

GitHub:https://github.com/asd123pwj/wordpress_spider

推荐参考:Python爬虫入门

这篇文章是在做出来后第六天写的, 写着写着我又想写爬虫了,因此又写了个WordPress转CSDN的,就不用我每次加前缀了。 python真好用噢。

2021/9/11更新:代码更新,增加处理注销用户的功能。
2021/9/21更新:代码更新,修复十天前加上的bug。
2021/10/2更新:代码更新,修复十一天漏掉的bug。增加数量选择。
2022/4/5更新:修改介绍,添加GitHub链接,添加转为exe的代码。

项目介绍

在中间的前言

  • 现在开了个栏目,翻译StackOverflow文章。
  • 但每次转换有点麻烦,复制粘贴改格式。
  • 于是就有了这篇。
  • 输入StackOverflow的问题网址,爬取后,转换成我的行文格式,并自动复制到剪切板。
  • 值得一提的是,WordPress的格式与html的格式还是挺像的,所以不需要做太大的改变。
  • 最后的成果也不错,只有两个问题:
    • 链接不是在新页面打开,而是在当前页面打开,这就要求我每次手动修改,但也只是十几秒的时间。
    • 这里的内容没有换行符,所以转换过来还需要我自己加,但我选的问题都是我能看懂的,所以加点换行符也不是大问题。

项目思路

  1. 爬取网页

    • 使用了urllib库。
    • 相关函数是ask_url_get_html(url)
    • 是很基础的用法,因为只爬取这一个页面,还是不用登陆就可以看到的页面,所以连cookie都不需要。
    • 提交一个链接后,爬取它的html文本。
  2. 网页与WordPress文章格式分析

    • 用到了BeautifulSoup库、re库。
    • 相关函数是get_data_from_html(html, answer_num)
    • 首先我的文章需要几个部分:
    • 标题及其链接。
    • 提问者ID,提问内容,提问者主页链接。
    • 回答者ID,回答内容,回答者主页链接,投票数。
    • 回答数量。
    • 这个后来删去了,爬取倒不是说麻烦,但就是想偷懒。
    • 然后就是观察在页面的哪些部分:
    • 先是比较简单的:
    • 标题:
      • 很容易获取,游览器右键审查元素找到的就是它。
      • 或者bs4的title也能获取。
    • 标题链接:
      • 这个是我给它的,所以不用获取。
    • 提问者ID:
      • 右键审查元素,它的类是‘user-details’。
    • 提问内容:
      • 类是‘s-prose js-post-body’。
    • 提问者链接:
      • 和前面提问者ID在一起。
    • 来到了回答者的部分,就比较麻烦了:
    • ID、内容、主页、投票数,它们的类与提问者都一致。
    • 也就是说,找到的第一个并不是我们需要的。
    • 但这还好,用re表达式匹配后,变成列表后也可以直接取得,所以还好。
    • 更麻烦的是内容匹配,因为有换行符,所以re不能完全匹配上所有内容,只有第一句。
    • 后来尝试了几个函数,最后使用str.replace(\n, ).replace('\r', '')以及re.DOTALL来删除所有换行符。
    • 然后可以正常匹配到完整内容了,但因为匹配用的表达式,会用上与下一个内容的标签,所以在匹配的时候需要多匹配一个内容。
      • 即,如果我要找第三个回答,因为提问内容标签与回答标签一致,
      • 第三个回答实际上是第四个内容,
      • 并且还因为要用到下一个内容的标签来匹配,所以实际上要匹配五个内容,
      • 才能找到第三个回答的内容。
    • 之后是转换成WordPress格式:
    • 只有内容需要转换,其他像ID、主页这些信息,实际上只是拼接而已。
    • 就来测试获取的能不能直接用:
    • 测试后发现可以,内容中代码的内容,恰好也是WordPress的代码块格式。
    • 所以匹配到的东西,复制粘贴就能用。
    • 但因为我是用WordPress里的列表来展示的,所以还需要进一步转换:
    • 选择一句复制粘贴来的内容,转换成WordPress列表,然后复制这个区块来分析。
    • 发现与html格式类似,是加了前缀与后缀,然后多个标签。
    • 大部分的内容是能直接用的,但如果回答者以代码块开头,就会导致格式出错:
    • StackOverflow里的代码块用到了,
    • 而每次我给WordPress列表添加内容的时候,还会加上
    • 就使得WordPress列表的第一行会直接向右移动两个缩进。
      • 有点像首行缩进,正常两个字符。
      • 而两个会导致四个字符作为首行缩进。
    • 但WordPress不允许这种情况出现,就会导致格式损坏。
      • 而且我也不可能用这么丑的格式。
    • 最终在这里的格式处理比较麻烦,有的格式不报错,但是不能正常显示。
    • 最后还是用较为复杂的内容转换成WordPress列表,加上换行和缩进,再一行行比较。
  3. 格式转换

    • 相关函数是convert_to_wordpress_format(page_info, url)

    • 就是字符串拼接。

    • 主要的难点在列表格式生成。

    • 这点就是前面最后内容匹配讲的。

    • 最后是用爬取到的内容,转换成WordPress列表格式,加上换行与缩进,再与自己生成的格式一行行比较。

  4. 输出为可用格式

    • 用到了pyperclip库。

    • 测试的时候使用print输出的,然后复制粘贴,但太过麻烦。

    • 于是选择转换成文本,但从pycharm文本中复制的内容,虽然和WordPress需要的格式一模一样吧,但是不会自动转换。

    • 于是找了一个pyperclip库,可以将文本粘贴至剪切板。

    • 虽然每次爬取页面后必须要及时粘贴吧,但也不是麻烦事。

    • 即便希望在处理后自行选择是否粘贴,也只需要加个选项,循环这个粘贴函数就好了。

    • 所以这个项目就大功告成了。

  5. 转换成exe文件

    • 用pyinstaller将文件转换成exe文件,避免每次都需要打开pycharm。

stackoverflow_spider.py

import re
import urllib.request
import urllib.error
from bs4 import BeautifulSoup
import pyperclip
import ssl

def main():
    url = input("Input stackoverflow question url: ")
    html = ask_url_get_html(url)
    print('Getting html file from url...')
    answer_num = 3
    answer_num = int(input('How many answers will be chosen(default:3): ')) or 3
    page_info = get_data_from_html(html, answer_num)
    print('Select the first 3 answers, processing...')
    wordpress_format = convert_to_wordpress_format(page_info, url)
    print('Success, the result will paste to the shear plate.')
    pyperclip.copy(wordpress_format)
    print('Copy success.')
    input("Press the ENTER key to close.")

def ask_url_get_html(url):
    #   从url中获取html文件
    head = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
    }
    request = urllib.request.Request(url, headers=head)
    html = None
    try:
        response = urllib.request.urlopen(request, context=ssl._create_unverified_context())
        html = response.read().decode("utf-8")
    except urllib.error.URLError as e:
        if hasattr(e, "code"):
            print(e.code)
        if hasattr(e, "reason"):
            print(e.reason)
    if html is None:
        input("Enter any key to continue")

    return html

def get_data_from_html(html, answer_num):
    #   html文本处理,收集标题、内容、作者名、作者主页链接、回答投票,得到并返回页面信息

    #   正则表达式
    find_title = re.compile('">(.*)</a>')
    find_description = re.compile('">(.*)</div>', re.DOTALL)
    find_author_name = re.compile('name">(.*?)</span>')
    find_author_link = re.compile('(href="(.*)">.*</a>)|(Person">(.*)<span)')
    find_vote_count = re.compile('data-value="(\d*?)"')

    soup = BeautifulSoup(html, "html.parser")
    #   标题匹配
    item = str(soup.find_all("a", class_="question-hyperlink", limit=1))
    title = re.findall(find_title, item)
    #   内容匹配,并修改/去除部分标签,方便后续处理
    item = str(soup.find_all("div", class_="s-prose js-post-body", limit=answer_num+2))
    item = item.replace("\n", "").replace('\r', '')
    item = item.replace('<ul>', '').replace('</ul>', '')
    item = item.replace('<li>', '').replace('</li>', '')
    item = item.replace("/pre>", "/p>").replace('<pre', '<p')
    item = item.replace("/p>", "/li>").replace("<p", "<li")
    # print(item)
    description = re.findall(find_description, item)
    description = description[0].split('</div>, <div class="s-prose js-post-body" itemprop="text">')
    #   作者名及作者连接匹配
    item = str(soup.find_all('div', class_="user-details", itemprop="author", limit=answer_num+1))
    # item = item.replace()
    author_name = re.findall(find_author_name, item)
    author_link = re.findall(find_author_link, item)\
    #   投票数匹配
    item = str(soup.find_all("div", class_='js-vote-count', limit=answer_num+1))
    vote_count = re.findall(find_vote_count, item)

    #   按序整理匹配信息
    page_info = []
    page_info.append(title[0])
    for pos in range(0, answer_num + 1):
        page_info.append(author_name[pos])
        if len(author_link) == answer_num + 1:
            page_info.append(author_link[pos][1])
        else:
            page_info.append("")
            if pos == 0: print('Link loss, maybe account cancellation?\nAll link replace with ""')
        page_info.append(description[pos])
        page_info.append(vote_count[pos])
    return page_info

def convert_to_wordpress_format(page_info, url):
    #   页面信息转WordPress格式文本
    wordpress_content = """
<blockquote class="wp-block-quote"><p><a rel="noreferrer noopener" 
href="https://mwhls.top/programming-language-learning/stackoverflow" 
target="_blank">stackoverflow热门问题目录</a></p><p>如有翻译问题欢迎评论指出,谢谢。</p></blockquote>
<!-- /wp:quote --><!-- wp:heading {"textAlign":"center","level":4} -->
<h4 class="has-text-align-center"><a rel="noreferrer noopener" href="
    """ + url + '" target="_blank">' + page_info[0] + '''</a></h4><!-- /wp:heading --><!-- wp:list --><ul><li><a rel="noreferrer noopener" href="https://stackoverflow.com''' + page_info[2] + '" target="_blank">' + page_info[1] + '''</a> asked:
    <ul>''' + page_info[3] + '</ul></li>' + '<li>Answers:'

    for pos in range(1, len(page_info) // 4):
        wordpress_content += '<ul><li><a rel="noreferrer noopener" href="https://stackoverflow.com'
        wordpress_content += page_info[4 * pos + 2]
        wordpress_content += '" target="_blank">'
        wordpress_content += page_info[4 * pos + 1]
        wordpress_content += '</a> - vote: '
        wordpress_content += page_info[4 * pos + 4]
        wordpress_content += '<ul>'
        wordpress_content += page_info[4 * pos + 3]
        wordpress_content += '</ul></li></ul>'

    wordpress_content += '</li></ul><!-- /wp:list -->'
    return wordpress_content

if __name__ == '__main__':
    main()

py2exe.py

import os
os.system("pyinstaller -F -c stackoverflow_spider.py")

版权声明:
作者:MWHLS
链接:https://mwhls.top/2175.html
来源:无镣之涯
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
< <上一篇
下一篇>>