Pythonによるブログアーカイブの自動化 — ウェブスクレイピングからExcel・Word出力まで

ソフトウェアエンジニアリングの履修課題として、ウェブ上からブログ記事を収集し、書式化されたドキュメントとして保存する処理を実装する機会は珍しくない。本稿では、Pythonを用いたスクレイピング技術に加え、ExcelおよびWord文書の自動生成方法について解説する。

開発環境の構築

本実装で使用する主要なライブラリは以下の通りである。requestsはHTTPリクエストの送信を担い、BeautifulSoupはHTMLの解析に利用する。xlwtはExcelファイルの作成に、python-docxはWord文書の生成に必要となる。さらに、urllib.requestによる詳細なリクエスト制御も実装に含める。

# 必要なライブラリのインストール
pip install requests beautifulsoup4 xlwt python-docx

ブログ記事スクレイピングの実装

スクレイピング処理は複数の関数に分割して実装することで、コードの可読性と保守性を高める。まず、対象ページのHTMLを取得する基本関数から実装を開始する。

# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
import urllib.request

class BlogArchiveScraper:
    """ブログアーカイブ用スクレイパークラス"""
    
    def __init__(self, base_url, max_pages=10):
        self.base_url = base_url
        self.max_pages = max_pages
        self.article_data = []
    
    def fetch_page_content(self, url):
        """指定URLからHTMLコンテンツを取得"""
        try:
            response = requests.get(url, timeout=10)
            response.encoding = 'utf-8'
            return response.content
        except requests.RequestException as e:
            print(f"取得エラー: {e}")
            return None
    
    def parse_list_page(self, html_content):
        """リストページから記事情報を抽出"""
        if html_content is None:
            return
        
        soup = BeautifulSoup(html_content, 'lxml')
        
        # 記事タイトルとURLの抽出
        title_elements = soup.find_all('div', class_='postTitle')
        for element in title_elements:
            link_tag = element.find('a')
            if link_tag:
                self.article_data.append({
                    'title': link_tag.get_text(strip=True),
                    'url': link_tag.get('href', '')
                })
        
        # 投稿日時の抽出
        desc_elements = soup.find_all('div', class_='postDesc')
        for idx, element in enumerate(desc_elements):
            if idx < len(self.article_data):
                text_content = element.get_text()
                if len(text_content) >= 19:
                    self.article_data[idx]['date'] = text_content[9:19]

詳細ページの処理

一覧ページから取得したURLを基に、各記事の詳細ページにアクセスして本文を取得する。urllib.requestを使用することで、より詳細なリクエスト制御が可能となる。

    def fetch_article_details(self):
        """詳細ページから記事本文を取得"""
        for index, article in enumerate(self.article_data):
            detail_url = article.get('url')
            if not detail_url:
                article['content'] = ''
                continue
                
            try:
                request = urllib.request.Request(detail_url)
                with urllib.request.urlopen(request, timeout=10) as response:
                    html_content = response.read().decode('utf-8')
                
                soup = BeautifulSoup(html_content, 'html.parser')
                content_div = soup.find(id='cnblogs_post_body')
                
                if content_div:
                    article['content'] = content_div.get_text(strip=True)
                else:
                    article['content'] = '本文取得失敗'
                    
            except Exception as e:
                print(f"詳細取得エラー (記事{index}): {e}")
                article['content'] = 'エラー発生'
    
    def execute_scraping(self):
        """スクレイピング処理の実行"""
        for page_num in range(1, self.max_pages + 1):
            target_url = f'{self.base_url}/default.html?page={page_num}'
            print(f'処理中: {target_url}')
            
            html = self.fetch_page_content(target_url)
            self.parse_list_page(html)
        
        self.fetch_article_details()
        return self.article_data

Excelファイルへの出力

収集したデータはxlwtライブラリを用いてExcelファイルとして保存する。書式設定を活用することで視認性を向上させることができる。

import xlwt
from datetime import datetime

class ExcelExporter:
    """Excel出力用クラス"""
    
    def __init__(self, filename):
        self.filename = filename
        self.workbook = xlwt.Workbook(encoding='utf-8')
        self.sheet = self.workbook.add_sheet('archive_data')
    
    def write_header(self):
        """ヘッダー行の書式設定と書き込み"""
        headers = ['記事タイトル', 'URL', '投稿日時', '記事本文']
        style = xlwt.easyxf('font: bold on;')
        
        for col, header_text in enumerate(headers):
            self.sheet.write(0, col, header_text, style)
    
    def write_data(self, data_list):
        """データ行の書き込み"""
        self.write_header()
        
        for row_idx, record in enumerate(data_list, start=1):
            self.sheet.write(row_idx, 0, record.get('title', ''))
            self.sheet.write(row_idx, 1, record.get('url', ''))
            self.sheet.write(row_idx, 2, record.get('date', ''))
            self.sheet.write(row_idx, 3, record.get('content', '')[:32767])
    
    def save(self):
        """ファイルを保存"""
        self.workbook.save(self.filename)
        print(f'Excelファイルを保存しました: {self.filename}')

Word文書への変換

Excelデータをテンプレートに基づいてWord文書に変換することで、より柔軟な書式運用が可能となる。python-docxを使用し、定型フォーマットへの自動書き込みを実装する。

from docx import Document
from docx.shared import Pt
import os

class WordDocumentGenerator:
    """Word文書生成用クラス"""
    
    def __init__(self, template_path):
        self.template_path = template_path
    
    def generate_from_excel(self, excel_path, output_dir):
        """Excelデータから複数のWord文書を生成"""
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        
        workbook = xlrd.open_workbook(excel_path)
        worksheet = workbook.sheet_by_name('archive_data')
        
        for row_idx in range(1, worksheet.nrows):
            doc = Document(self.template_path)
            
            title = str(worksheet.cell_value(row_idx, 0))
            url = str(worksheet.cell_value(row_idx, 1))
            date = str(worksheet.cell_value(row_idx, 2))
            content = str(worksheet.cell_value(row_idx, 3))
            
            self._fill_template(doc, title, url, date, content)
            
            output_filename = f'{output_dir}/archive_{row_idx}_{title[:20]}.docx'
            doc.save(output_filename)
            print(f'生成完了: {output_filename}')
    
    def _fill_template(self, doc, title, url, date, content):
        """テンプレートへのデータ挿入"""
        # タイトルの置き換え
        for paragraph in doc.paragraphs:
            if '{title}' in paragraph.text:
                paragraph.text = paragraph.text.replace('{title}', title)
            if '{url}' in paragraph.text:
                paragraph.text = paragraph.text.replace('{url}', url)
            if '{date}' in paragraph.text:
                paragraph.text = paragraph.text.replace('{date}', date)
        
        # コンテンツの挿入(テーブル形式)
        tables = doc.tables
        if tables:
            target_cell = tables[0].cell(0, 0)
            target_cell.text = content

メイン処理の実行

これまでに実装した各クラスを連携させ、一連の処理を完結させる。エラー処理を含めることで、安定した運用を実現する。

def main():
    """メイン処理"""
    blog_url = 'http://www.cnblogs.com/example_user'
    output_excel = 'archive_data.xlsx'
    word_template = 'archive_template.dotx'
    output_word_dir = 'output_documents'
    
    try:
        # スクレイピングの実行
        scraper = BlogArchiveScraper(blog_url, max_pages=10)
        scraped_data = scraper.execute_scraping()
        
        # Excel出力
        exporter = ExcelExporter(output_excel)
        exporter.write_data(scraped_data)
        exporter.save()
        
        # Word文書生成(オプション)
        generator = WordDocumentGenerator(word_template)
        generator.generate_from_excel(output_excel, output_word_dir)
        
        print('アーカイブ処理が正常完了しました')
        
    except Exception as e:
        print(f'処理中にエラーが発生しました: {e}')

if __name__ == '__main__':
    main()

実装上の注意点

ウェブスクレイピングを実装する際には、対象サイトの利用ポリシーを確認し遵守することが重要である。過剰なアクセスはサーバー負荷を高める原因となるため、適切なsleep処理を挿入することを推奨する。また、robots.txtの設定も確認すべきである。

本稿で示した実装例は基礎的なものであり、実際の運用環境ではログ出力機構の追加、設定ファイルの外部化、より堅牢なエラー処理など、さらなる拡張が必要となる場合がある。

タグ: Python webscraping BeautifulSoup requests Excel

5月22日 02:33 投稿