Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What is going on with this article?
@autumn_nsn

[Python]全角と半角が混在するテキストに空白を入れて横幅を揃える関数

はじめに

ヤフーニュースのサイトからヘッドラインの一覧とURLを取得して、各項目を1行で表示するプログラムを作成していたのですが、URLの列をきれいに揃えるのにちょっと苦労したので、今後のために記事を書いておこうと思います。

以下のようなサイトからデータを取得します。
2020-10-15_09h07_43.png

最終的に取得するテキストは以下の通りです。
2020-10-15_09h13_18.png

開発環境

Python 3.7 を使用します。
開発環境はVisual Studio Community 2019です。

コード

import requests
import unicodedata
from urllib.parse import urljoin
from bs4 import BeautifulSoup

def main():
    base_url = 'https://news.yahoo.co.jp/'
    categories = {
        '主要': '',
        '国内': 'categories/domestic',
        #'エンタメ': 'categories/entertainment',
        #'国際': 'categories/world',
        #'経済': 'categories/business',
        }

    # カテゴリーごとにループ処理する
    for cat in categories:
        url = urljoin(base_url, categories[cat])

        r = requests.get(url)
        soup = BeautifulSoup(r.content, 'lxml') # html.parser

        ul_tag = soup.find('div', class_='topicsList')\
                     .find('ul', class_='topicsList_main')

        print(f'==={cat}===')

        for item in ul_tag.find_all('li', class_='topicsListItem'):
            a = item.find('a')
            topic_url = a['href']
            topic_headline = a.text.strip()

            #print(f'{topic_headline:<18}[{topic_url}]')
            text = text_align(topic_headline, 30)
            print(f'{text}[{topic_url}]')

        print()

def get_han_count(text):
    '''
    全角文字は「2」、半角文字は「1」として文字列の長さを計算する
    '''
    count = 0

    for char in text:
        if unicodedata.east_asian_width(char) in 'FWA':
            count += 2
        else:
            count += 1

    return count

def text_align(text, width, *, align=-1, fill_char=' '):
    '''
    全角/半角が混在するテキストを
    指定の長さ(半角換算)になるように空白などで埋める

    width: 半角換算で文字数を指定
    align: -1 -> left, 1 -> right
    fill_char: 埋める文字を指定

    return: 空白を埋めたテキスト('abcde     ')
    '''

    fill_count = width - get_han_count(text)
    if (fill_count <= 0): return text

    if align < 0:
        return text + fill_char*fill_count
    else:
        return fill_char*fill_count + text

if __name__ == '__main__':
    main()

当初、出力テキストのフォーマットは以下のようにしていました。

for item in ul_tag.find_all('li', class_='topicsListItem'):
    a = item.find('a')
    topic_url = a['href']
    topic_headline = a.text.strip()

    # このコードではURLの列がずれてしまいます。
    print(f'{topic_headline:<18}[{topic_url}]')

これですと、以下のように出力になってしまいます。
2020-10-15_09h23_08.png

print(f'{topic_headline:<18}[{topic_url}]')のような指定では、全角文字と半角文字を区別せずに扱ってしまいます。

そこで、テキストの全角と半角を区別して必要な空白を挿入するための関数を作成しました。

def get_han_count(text):
    '''
    全角文字は「2」、半角文字は「1」として文字列の長さを計算する
    '''
    count = 0

    for char in text:
        if unicodedata.east_asian_width(char) in 'FWA':
            count += 2
        else:
            count += 1

    return count

def text_align(text, width, *, align=-1, fill_char=' '):
    '''
    全角/半角が混在するテキストを
    指定の長さ(半角換算)になるように空白などで埋める

    width: 半角換算で文字数を指定
    align: -1 -> left, 1 -> right
    fill_char: 埋める文字を指定

    return: 空白を埋めたテキスト('abcde     ')
    '''

    fill_count = width - get_han_count(text)
    if (fill_count <= 0): return text

    if align < 0:
        return text + fill_char*fill_count
    else:
        return fill_char*fill_count + text

最終的には、以下のようなコードで書式を整えます。

for item in ul_tag.find_all('li', class_='topicsListItem'):
    a = item.find('a')
    topic_url = a['href']
    topic_headline = a.text.strip()

    text = text_align(topic_headline, 30)
    print(f'{text}[{topic_url}]')

これで、無事解決しました。
text_align()は一応、左側に空白を入れるオプションと、空白以外の記号も指定できるようにしました。

テキストエディターなどに出力する方法

ところで、Pythonのプログラムからの出力は通常はコマンドプロンプトになると思いますが、今回のケースのような場合にはテキストエディターやワープロなどに書き出して保存したいと思うかもしれません。

そのような場合には、ペースターというソフトを使うとエディターなどのキャレット位置に直接ペーストすることができます。
2020-10-15_09h50_53.png

すると、以下のように直接データがペーストされます。
2020-10-15_09h13_18.png

終わりに

今回はテキストのフォーマットに関するテーマだったので、スクレイピングに関するコードの解説はしませんでしたが、ほぼfind()メソッドだけで事足りました。

なお、上記のソースコードは自由に使って頂いて構いませんが、その際には自己責任でお願いします。

参考サイト

全角と半角を区別する関数(unicodedata.east_asian_width())の使い方につきましては、以下のサイトを参考にさせて頂きました。

Pythonで半角1文字、全角2文字として文字数(幅)カウント

なお、Webスクレイピングを行う場合には、対象サイトのrobots.txtを必ず確認しておきましょう。

news.yahoo.co.jp/robots.txt
User-agent: *
Disallow: /comment/plugin/
Disallow: /comment/violation/
Disallow: /polls/widgets/
Disallow: /articles/*/comments
Sitemap: https://news.yahoo.co.jp/sitemaps.xml
Sitemap: https://news.yahoo.co.jp/sitemaps/article.xml
Sitemap: https://news.yahoo.co.jp/byline/sitemap.xml
Sitemap: https://news.yahoo.co.jp/polls/sitemap.xml
2
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What is going on with this article?