Python爬取StackOverflow问题页面并转换为WordPress可用格式

推荐参考:Python爬虫入门

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

项目介绍

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

项目思路

  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、主页这些信息,实际上只是拼接而已。
      • 就来测试获取的能不能直接用:
        • 测试后发现可以,内容中<code>代码</code>的内容,恰好也是WordPress的代码块格式。
        • 所以匹配到的东西,复制粘贴就能用。
      • 但因为我是用WordPress里的列表来展示的,所以还需要进一步转换:
        • 选择一句复制粘贴来的内容,转换成WordPress列表,然后复制这个区块来分析。
        • 发现与html格式类似,是加了前缀与后缀,然后多个<ul>标签。
      • 大部分的内容是能直接用的,但如果回答者以代码块开头,就会导致格式出错:
        • StackOverflow里的代码块用到了<ul>,
        • 而每次我给WordPress列表添加内容的时候,还会加上<ul>
        • 就使得WordPress列表的第一行会直接向右移动两个缩进。
          • 有点像首行缩进,正常两个字符。
          • 而两个<ul>会导致四个字符作为首行缩进。
        • 但WordPress不允许这种情况出现,就会导致格式损坏。
          • 而且我也不可能用这么丑的格式。
        • 最终在这里的格式处理比较麻烦,有的格式不报错,但是不能正常显示。
        • 最后还是用较为复杂的内容转换成WordPress列表,加上换行和缩进,再一行行比较。
  3. 格式转换
    • 相关函数是convert_to_wordpress_format(page_info, url)
    • 就是字符串拼接。
    • 主要的难点在列表格式生成。
      • 这点就是前面最后内容匹配讲的。
      • 最后是用爬取到的内容,转换成WordPress列表格式,加上换行与缩进,再与自己生成的格式一行行比较。
  4. 输出为可用格式
    • 用到了pyperclip库。
    • 测试的时候使用print输出的,然后复制粘贴,但太过麻烦。
    • 于是选择转换成文本,但从pycharm文本中复制的内容,虽然和WordPress需要的格式一模一样吧,但是不会自动转换。
    • 于是找了一个pyperclip库,可以将文本粘贴至剪切板。
    • 虽然每次爬取页面后必须要及时粘贴吧,但也不是麻烦事。
    • 即便希望在处理后自行选择是否粘贴,也只需要加个选项,循环这个粘贴函数就好了。
    • 所以这个项目就大功告成了。
  5. 转换成exe文件
    • 用pyinstaller将文件转换成exe文件,避免每次都需要打开pycharm。

代码

  • 113行,如果不为了美观的话可以再少点。
  • 效率不错,网络好的话一秒就处理好了。
import re
import urllib.request
import urllib.error
from bs4 import BeautifulSoup
import pyperclip


def main():
    url = input("Input stackoverflow question url:")
    html = ask_url_get_html(url)
    print('Getting html file from url...')
    answer_num = 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)
    try:
        response = urllib.request.urlopen(request)
        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)
    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('">(.*)</a>')
    find_author_link = re.compile('href="(.*)">.*</a>')
    find_vote_count = re.compile('t">(\d*)</div>')

    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 grid--cell fc-black-500 fs-title grid fd-column ai-center', 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])
        page_info.append(author_link[pos])
        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 = """
<!-- wp:quote -->
<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()

You may also like...

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注