Nokogiriとは?Webスクレイピングの強力な味方
RubyのWebスクレイピングライブラリの定番として選ばれる理由
Nokogiriは、HTMLやXMLを解析するためのRubyライブラリであり、その名前は日本語の「鋸(のこぎり)」に由来します。木を切るように、HTMLやXMLドキュメントを自在に解析できるという意味が込められています。
Nokogiriが多くのRubyエンジニアから選ばれる理由は、以下の特徴にあります:
- 高速な解析処理
- ネイティブのC言語実装により、大量のHTMLやXMLを高速に処理
- メモリ効率の良い実装により、大規模なドキュメントも扱える
- 直感的なAPI設計
require 'nokogiri' require 'open-uri' # HTMLの取得と解析 doc = Nokogiri::HTML(URI.open('https://example.com')) # CSSセレクタで要素を取得 titles = doc.css('h1.title') # XPath式での要素取得 links = doc.xpath('//a[@class="link"]')
- 豊富な検索機能
- CSSセレクタとXPathの両方をサポート
- 要素の検索、属性の取得、テキストの抽出が容易
- 階層構造を考慮した柔軟な要素指定が可能
- 強力なドキュメント操作
- 要素の追加、削除、置換が可能
- 属性の操作や内容の変更が容易
- DOMツリーの走査機能が充実
Nokogiriがサポートするパーサーとその特徴
Nokogiriは複数のパーサーをサポートしており、用途に応じて適切なものを選択できます:
- HTML4パーサー
- 標準的なHTMLドキュメントの解析に最適
- 壊れたHTMLも自動修正して解析
- 最も一般的に使用されるパーサー
# HTML4パーサーの使用例 doc = Nokogiri::HTML4(html_content)
- HTML5パーサー
- モダンなHTML5文書の解析に対応
- より厳密なHTML5仕様に準拠
- 新しいHTML5要素やセマンティクスをサポート
# HTML5パーサーの使用例 doc = Nokogiri::HTML5(html_content)
- XMLパーサー
- 厳密なXML文書の解析に使用
- 名前空間のサポート
- DTDやスキーマの検証が可能
# XMLパーサーの使用例 doc = Nokogiri::XML(xml_content)
- SAXパーサー
- イベントドリブンな解析が可能
- 大規模なファイルを効率的に処理
- メモリ使用量を抑えた処理が可能
# SAXパーサーの使用例 class MyHandler < Nokogiri::XML::SAX::Document def start_element(name, attrs = []) puts "開始要素: #{name}" end end parser = Nokogiri::XML::SAX::Parser.new(MyHandler.new) parser.parse(xml_content)
各パーサーは特定のユースケースに最適化されており、プロジェクトの要件に応じて適切なものを選択することで、効率的なスクレイピングを実現できます。特に、一般的なWebスクレイピングではHTML4パーサーが最も使用されますが、より特殊な要件がある場合は他のパーサーの使用を検討することをお勧めします。
環境構築から始めるNokogiri入門
gem installからbundlerでの管理まで
Nokogiriの環境構築には複数の方法がありますが、ここでは最も一般的な手順を解説します。
- 直接インストール
# 最新版のインストール gem install nokogiri # 特定のバージョンを指定してインストール gem install nokogiri -v '1.15.5'
- Bundlerを使用したインストール
# Gemfileに追加 source 'https://rubygems.org' gem 'nokogiri' # もしくはバージョンを指定 gem 'nokogiri', '~> 1.15.5' # インストールの実行 bundle install
- プロジェクトでの使用開始
# Bundlerを使用する場合 require 'bundler/setup' require 'nokogiri' # 直接requireする場合 require 'nokogiri'
- 動作確認
# バージョン確認 puts Nokogiri::VERSION # 簡単な解析テスト doc = Nokogiri::HTML('<h1>Hello, Nokogiri!</h1>') puts doc.at_css('h1').text # => "Hello, Nokogiri!"
よくあるインストールエラーとその解決方法
Nokogiriのインストール時には、システムの環境によって様々なエラーが発生する可能性があります。以下に主な問題と解決方法を示します:
- ネイティブエクステンション関連のエラー エラーメッセージ例:
ERROR: Failed to build gem native extension.
解決方法:
# Ubuntuの場合 sudo apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev # macOSの場合 xcode-select --install # インストール後、再度gem installを実行 gem install nokogiri
- libxml2/libxslt関連のエラー エラーメッセージ例:
ERROR: cannot find library 'libxml2'
解決方法:
# Ubuntuの場合 sudo apt-get install libxml2-dev libxslt-dev # macOSの場合(Homebrewを使用) brew install libxml2 libxslt # システムのlibxml2を使用する場合 gem install nokogiri -- --use-system-libraries
- SSL証明書関連のエラー エラーメッセージ例:
SSL_connect returned=1 errno=0 state=error: certificate verify failed
解決方法:
# 証明書の更新(RubyGemsの場合) gem update --system # または環境変数で証明書のパスを指定 export SSL_CERT_FILE=/path/to/cacert.pem
- メモリ不足エラー エラーメッセージ例:
Failed to allocate memory (NoMemoryError)
解決方法:
# swapファイルの作成(Linuxの場合) sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile
インストールに問題が発生した場合は、以下の手順で対処することをお勧めします:
- エラーメッセージを注意深く読む
- システムの依存関係を確認
- 必要なライブラリをインストール
- 環境変数の設定を確認
- 必要に応じてシステムを再起動
これらの手順で解決しない場合は、Nokogiriの公式ドキュメントやGitHubのIssuesで追加の情報を確認することをお勧めします。
Nokogiri の基本操作マスター
HTML ドキュメントの読み込みと解析
Nokogiriでは、様々な方法でHTMLドキュメントを読み込むことができます。以下に主要な方法を示します:
- 文字列からの読み込み
# HTML文字列からドキュメントを作成 html = '<html><body><h1>Hello World</h1></body></html>' doc = Nokogiri::HTML(html) # エンコーディングを指定する場合 doc = Nokogiri::HTML(html, nil, 'UTF-8')
- ファイルからの読み込み
# ローカルファイルを読み込む doc = Nokogiri::HTML(File.open('index.html')) # open-uriを使用してWebページを読み込む require 'open-uri' doc = Nokogiri::HTML(URI.open('https://example.com'))
- フラグメントの解析
# HTMLフラグメントを解析 fragment = Nokogiri::HTML.fragment('<div>部分的なHTML</div>') # フラグメント内の要素を操作 fragment.css('div').each do |div| puts div.content end
CSS セレクタを使用した要素の取得テクニック
CSSセレクタを使用すると、直感的に要素を取得できます:
- 基本的なセレクタの使用
# タグ名による取得 doc.css('h1') # すべてのh1タグ # クラスによる取得 doc.css('.content') # contentクラスを持つ要素 # IDによる取得 doc.css('#main') # mainというIDを持つ要素 # 属性による取得 doc.css('a[href]') # href属性を持つすべてのaタグ doc.css('img[alt="logo"]') # alt属性が"logo"であるimg要素
- 複合セレクタの活用
# 子孫セレクタ doc.css('div.content p') # contentクラスのdiv内のすべてのp要素 # 直接の子要素 doc.css('ul > li') # ulの直接の子であるli要素 # 複数条件の組み合わせ doc.css('div.content, div.sidebar') # contentクラスまたはsidebarクラスのdiv
- 要素の操作
# テキスト内容の取得 doc.css('h1').each do |element| puts element.text # テキスト内容を出力 end # 属性値の取得 doc.css('a').each do |link| puts link['href'] # href属性の値を出力 end # 属性の設定 doc.css('img').each do |img| img['loading'] = 'lazy' # loading属性を設定 end
XPath 式を活用した高度な要素指定
XPath式を使用すると、より細かい要素の指定が可能です:
- 基本的なXPath式
# 絶対パス doc.xpath('/html/body/div') # htmlのbody内のdivを取得 # 相対パス doc.xpath('.//p') # 現在のノードから見て任意の階層のp要素 # 属性による指定 doc.xpath('//div[@class="content"]') # contentクラスを持つdiv
- 高度な条件指定
# テキスト内容による指定 doc.xpath('//p[contains(text(), "重要")]') # "重要"を含むp要素 # 位置による指定 doc.xpath('//ul/li[1]') # 各ulの最初のli要素 doc.xpath('//ul/li[last()]') # 各ulの最後のli要素 # 複数条件の組み合わせ doc.xpath('//div[@class="content" and contains(@id, "main")]')
- カスタム関数の活用
# 名前空間の登録 doc.xpath('//custom:element', 'custom' => 'http://example.com/namespace') # カスタムXPath関数の定義 module MyCustomFunctions def filter_by_length(nodes, min_length) nodes.find_all { |node| node.content.length >= min_length.to_i } end end # 関数の登録と使用 Nokogiri::XML::Document.send(:include, MyCustomFunctions) doc.xpath('//p[filter_by_length(., 100)]')
これらの基本操作を組み合わせることで、複雑なHTMLドキュメントから必要な情報を効率的に抽出できます。また、CSSセレクタとXPath式は状況に応じて使い分けることで、より柔軟なスクレイピングが可能になります。
実践的なスクレイピング実務テクニック
複数ページの効率的なクローリング方法
大規模なWebサイトのスクレイピングでは、複数ページを効率的に処理する必要があります。以下に実践的なテクニックを紹介します:
- ページネーション処理
require 'nokogiri' require 'open-uri' require 'uri' class PaginationCrawler def initialize(base_url) @base_url = base_url @processed_urls = Set.new @results = [] end def crawl(max_pages: 10) current_page = 1 while current_page <= max_pages url = "#{@base_url}?page=#{current_page}" break unless process_page(url) current_page += 1 # クロール間隔を設定 sleep(1) end @results end private def process_page(url) return false if @processed_urls.include?(url) doc = Nokogiri::HTML(URI.open(url)) @processed_urls.add(url) # ページ内のデータを抽出 items = doc.css('.item').map do |item| { title: item.at_css('.title')&.text&.strip, price: item.at_css('.price')&.text&.strip } end @results.concat(items) # 次のページが存在するか確認 !!doc.at_css('.next-page') rescue OpenURI::HTTPError => e puts "ページ取得エラー: #{url} - #{e.message}" false end end
- 並行処理による高速化
require 'parallel' def parallel_crawl(urls, max_concurrency: 3) Parallel.map(urls, in_processes: max_concurrency) do |url| begin doc = Nokogiri::HTML(URI.open(url)) # データ抽出処理 { url: url, data: extract_data(doc), status: 'success' } rescue => e { url: url, error: e.message, status: 'error' } end end end
動的コンテンツへの対応策
JavaScriptで動的に生成されるコンテンツへの対応方法を説明します:
- APIエンドポイントの活用
require 'json' require 'net/http' def fetch_api_data(api_url, params = {}) uri = URI(api_url) uri.query = URI.encode_www_form(params) response = Net::HTTP.get_response(uri) if response.is_a?(Net::HTTPSuccess) JSON.parse(response.body) else raise "API呼び出しエラー: #{response.code}" end end
- ヘッドレスブラウザとの連携
require 'selenium-webdriver' def scrape_dynamic_content(url) options = Selenium::WebDriver::Chrome::Options.new options.add_argument('--headless') driver = Selenium::WebDriver.create(:chrome, options: options) begin driver.get(url) # JavaScriptの実行完了を待機 wait = Selenium::WebDriver::Wait.new(timeout: 10) wait.until { driver.execute_script('return document.readyState') == 'complete' } # HTML取得とNokogiri解析 doc = Nokogiri::HTML(driver.page_source) extract_data(doc) ensure driver.quit end end
バリデーションとエラーハンドリング
堅牢なスクレイピングシステムには、適切なバリデーションとエラー処理が不可欠です:
- データバリデーション
class DataValidator def self.validate_item(item) errors = [] errors << "タイトルが空です" if item[:title].nil? || item[:title].empty? errors << "価格が不正です" unless valid_price?(item[:price]) errors << "URLが不正です" unless valid_url?(item[:url]) { valid: errors.empty?, errors: errors, data: item } end private def self.valid_price?(price) return false unless price.is_a?(String) price.match?(/^\d+,?\d*円$/) end def self.valid_url?(url) uri = URI.parse(url) uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) rescue URI::InvalidURIError false end end
- エラー処理とリトライ機能
class ResilientScraper MAX_RETRIES = 3 RETRY_DELAY = 2 # 秒 def scrape_with_retry(url) retries = 0 begin doc = Nokogiri::HTML(URI.open(url)) process_document(doc) rescue OpenURI::HTTPError => e if e.message =~ /429|5\d\d/ && retries < MAX_RETRIES retries += 1 sleep(RETRY_DELAY * retries) retry else raise end rescue StandardError => e log_error(url, e) raise end end private def log_error(url, error) # エラーログの記録 puts "[#{Time.now}] Error scraping #{url}: #{error.message}" puts error.backtrace.join("\n") end end
これらのテクニックを組み合わせることで、実運用に耐えうる堅牢なスクレイピングシステムを構築できます。特に、エラー処理とバリデーションは本番環境での安定性を確保する上で重要な役割を果たします。
パフォーマンスとスケーラビリティの改善
メモリ使用量の最適化テクニック
大規模なHTMLドキュメントを処理する際は、メモリ使用量の最適化が重要です。以下に効果的な手法を紹介します:
- ストリーム処理の活用
require 'nokogiri' class StreamParser < Nokogiri::XML::SAX::Document def initialize @current_depth = 0 @data = [] end def start_element(name, attributes = []) @current_depth += 1 # 特定の要素のみを処理 if name == 'article' && @current_depth == 2 @current_article = {} attributes.each do |key, value| @current_article[key] = value end end end def end_element(name) if name == 'article' && @current_depth == 2 process_article(@current_article) @current_article = nil end @current_depth -= 1 end private def process_article(article) # 必要な処理のみを実行 @data << article end end # 使用例 parser = Nokogiri::XML::SAX::Parser.new(StreamParser.new) parser.parse(File.open("large_file.xml"))
- メモリ解放の明示的制御
class MemoryEfficientParser def process_large_document(file_path) batch_size = 1000 current_batch = [] File.open(file_path) do |file| doc = Nokogiri::HTML(file) doc.css('article').each do |article| current_batch << extract_data(article) if current_batch.size >= batch_size process_batch(current_batch) current_batch = [] GC.start # 明示的なガベージコレクション end end # 残りのバッチを処理 process_batch(current_batch) unless current_batch.empty? end end private def extract_data(article) # 必要なデータのみ抽出 { title: article.at_css('h1')&.text, content: article.at_css('p')&.text } end def process_batch(batch) # バッチ処理の実装 batch.each do |data| # データの保存や加工 end end end
メモリ処理による高速化の実現
パフォーマンスを最大限に引き出すための高度な最適化テクニックを紹介します:
- 並列処理の最適化
require 'parallel' require 'oj' # 高速なJSONパーサー class ParallelProcessor def initialize(worker_count = 4) @worker_count = worker_count @mutex = Mutex.new @results = [] end def process_documents(urls) Parallel.each(urls, in_processes: @worker_count) do |url| result = process_single_document(url) @mutex.synchronize do @results << result end end @results end private def process_single_document(url) doc = Nokogiri::HTML(URI.open(url)) # メモリ効率の良いデータ抽出 { url: url, data: extract_minimal_data(doc) } end def extract_minimal_data(doc) # 必要最小限のデータのみを抽出 doc.css('target_element').map do |element| element.text.strip end ensure # 明示的にメモリを解放 doc = nil GC.start end end
- キャッシュの活用
require 'redis' class CachedParser def initialize @redis = Redis.new @cache_ttl = 3600 # 1時間 end def parse_with_cache(url) cache_key = "parsed_content:#{Digest::MD5.hexdigest(url)}" # キャッシュの確認 if cached = @redis.get(cache_key) return Oj.load(cached) end # 新規パース result = parse_fresh_content(url) # キャッシュの保存 @redis.setex(cache_key, @cache_ttl, Oj.dump(result)) result end private def parse_fresh_content(url) doc = Nokogiri::HTML(URI.open(url)) # メモリ効率を考慮したパース処理 { title: doc.at_css('title')&.text, content: doc.at_css('main')&.text, timestamp: Time.now.to_i } end end
- プロファイリングとモニタリング
require 'memory_profiler' class ProfilingParser def self.profile_parsing(url) report = MemoryProfiler.report do doc = Nokogiri::HTML(URI.open(url)) yield(doc) if block_given? end # メモリ使用状況のレポート出力 report.pretty_print(to_file: 'memory_profile.txt') end def self.monitor_memory_usage initial_memory = GetProcessMem.new.mb yield if block_given? final_memory = GetProcessMem.new.mb memory_difference = final_memory - initial_memory puts "メモリ使用量の変化: #{memory_difference.round(2)}MB" end end
これらの最適化テクニックを適切に組み合わせることで、大規模なスクレイピングプロジェクトでも安定したパフォーマンスを実現できます。特に、メモリ使用量の制御とキャッシュ戦略は、運用環境での安定性を確保する上で重要な要素となります。
セキュリティとマナーの適切事項
正しいリクエスト対策の設定
Webスクレイピングを行う際は、対象サイトへの負荷とセキュリティを考慮した適切な設定が必要です:
- リクエストヘッダーの適切な設定
require 'nokogiri' require 'open-uri' class ResponsibleScraper def initialize @headers = { 'User-Agent' => 'MyBot/1.0 (contact@example.com)', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Language' => 'ja,en-US;q=0.9,en;q=0.8' } end def fetch_page(url) URI.open(url, @headers) do |f| Nokogiri::HTML(f) end rescue OpenURI::HTTPError => e handle_http_error(e, url) end private def handle_http_error(error, url) case error.message when /429/ puts "レート制限に達しました: #{url}" sleep(300) # 5分待機 when /403/ puts "アクセスが拒否されました: #{url}" else puts "エラーが発生しました: #{error.message}" end nil end end
- アクセス頻度の制御
class RateLimiter def initialize(requests_per_minute: 20) @interval = 60.0 / requests_per_minute @last_request = Time.now - @interval end def throttle wait_time = @interval - (Time.now - @last_request) sleep(wait_time) if wait_time > 0 @last_request = Time.now yield if block_given? end end # 使用例 limiter = RateLimiter.new(requests_per_minute: 30) urls.each do |url| limiter.throttle do # スクレイピング処理 end end
robots.txtの尊重とサイトポリシーの確認
Webサイトのポリシーを尊重することは、倫理的なスクレイピングの基本です:
- robots.txtの解析と遵守
require 'robotstxt' class PolicyCompliantScraper def initialize(base_url) @base_url = base_url @parser = initialize_robots_parser end def can_crawl?(url) return false unless @parser path = URI.parse(url).path @parser.allowed?(path, user_agent: 'MyBot/1.0') end private def initialize_robots_parser robots_url = URI.join(@base_url, '/robots.txt') robots_content = URI.open(robots_url).read Robotstxt.parse(robots_content, user_agent: 'MyBot/1.0') rescue OpenURI::HTTPError puts "robots.txtが見つかりませんでした: #{@base_url}" nil end end # 使用例 scraper = PolicyCompliantScraper.new('https://example.com') if scraper.can_crawl?('/articles/123') # スクレイピング処理 end
- サイトポリシーの確認と遵守
class SitePolicy def self.check_terms_of_service(url) domain = URI.parse(url).host tos_paths = ['/terms', '/tos', '/terms-of-service'] tos_paths.each do |path| tos_url = "https://#{domain}#{path}" begin response = URI.open(tos_url) puts "利用規約を確認してください: #{tos_url}" return true rescue OpenURI::HTTPError next end end puts "利用規約が見つかりませんでした。サイト管理者に確認することをお勧めします。" false end def self.validate_content_usage(content) restricted_patterns = [ /confidential/i, /private/i, /proprietary/i ] restricted_patterns.each do |pattern| if content.match?(pattern) raise "制限付きコンテンツが検出されました" end end end end
- エラー時の適切な対応
class EthicalScraper MAX_RETRIES = 3 BACKOFF_FACTOR = 2 def scrape_with_respect(url) retries = 0 begin return unless SitePolicy.check_terms_of_service(url) doc = ResponsibleScraper.new.fetch_page(url) content = extract_content(doc) SitePolicy.validate_content_usage(content) content rescue => e retries += 1 if retries <= MAX_RETRIES sleep(BACKOFF_FACTOR ** retries) retry else log_error(url, e) nil end end end private def log_error(url, error) File.open('scraping_errors.log', 'a') do |f| f.puts "[#{Time.now}] Error scraping #{url}: #{error.message}" end end end
これらの対策を実装することで、対象サイトに負荷をかけずに、かつ倫理的な方法でスクレイピングを実施できます。また、適切なエラー処理とログ記録により、問題が発生した際の対応も容易になります。
トラブルシューティングガイド
エンコード関連の問題解決
Nokogiriでよく遭遇するエンコーディング問題とその解決方法を説明します:
- 文字化けへの対処
require 'nokogiri' require 'open-uri' # 基本的な文字コード対応 html = URI.open('https://example.jp').read doc = Nokogiri::HTML(html, nil, 'Shift_JIS') # 文字コードを明示的に指定する場合 doc = Nokogiri::HTML(html.force_encoding('Shift_JIS').encode('UTF-8')) # メタタグから文字コードを判定する場合 doc = Nokogiri::HTML(html) do |config| config.strict.noent end charset = doc.at_css('meta[charset]')&.[]('charset') || doc.at_css('meta[http-equiv="Content-Type"]')&.[]('content')&.match(/charset=(.+?)($|;)/i)&.[](1)
- エンコーディングエラーの解決例
def safe_encode(text, from_encoding = 'Shift_JIS') text.force_encoding(from_encoding) .encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') rescue Encoding::InvalidByteSequenceError text.force_encoding('UTF-8') end # 使用例 html = URI.open('https://example.jp').read safe_html = safe_encode(html) doc = Nokogiri::HTML(safe_html)
パース失敗時の対処法
HTMLのパースに失敗した際の一般的な対処方法を紹介します:
- 壊れたHTMLの修正
# 不正なタグの処理 html = html.gsub(/<\/?[^>]*>/) do |tag| if tag =~ /<\/?(?:div|p|span|a|img|h[1-6]|ul|ol|li)[\s>]/ tag else '' # 不明なタグを削除 end end # 閉じタグの補完 html = html.gsub(/<(div|p|span)((?!>).)*?>(?!.*?<\/\1>)/) do |match| "#{match}</#{$1}>" end doc = Nokogiri::HTML(html)
- 一般的なエラーと対処法
- 空のドキュメントエラー
def parse_with_validation(html) doc = Nokogiri::HTML(html) if doc.css('body').empty? raise "有効なHTML内容が見つかりません" end doc rescue => e puts "パースエラー: #{e.message}" nil end
- 無効なセレクタエラー
def safe_css_select(doc, selector) doc.css(selector) rescue Nokogiri::CSS::SyntaxError => e puts "無効なCSSセレクタ: #{selector}" puts "エラー: #{e.message}" [] # エラー時は空配列を返す end # 使用例 elements = safe_css_select(doc, 'div.content > p')
- よくあるトラブルと解決策
- セレクタが要素を取得できない
# 問題のある例 doc.css('.specific-class') # 要素が見つからない # 解決策:階層を確認 puts doc.at_css('.specific-class')&.parent&.to_html # 解決策:クラス名の完全一致を確認 doc.css('[class="specific-class"]')
- 動的コンテンツが取得できない
# JavaScriptで生成される内容は通常のNokogiriでは取得できない # 解決策1: APIを使用 require 'json' require 'net/http' response = Net::HTTP.get(URI('https://api.example.com/data')) data = JSON.parse(response) # 解決策2: Seleniumなどのヘッドレスブラウザを使用 require 'selenium-webdriver' driver = Selenium::WebDriver.for :chrome, options: options driver.get(url) html = driver.page_source doc = Nokogiri::HTML(html)
デバッグのためのヒント:
- ドキュメントの構造を確認
# HTMLの構造を確認 puts doc.to_html # 特定の要素の周辺構造を確認 element = doc.at_css('.target') puts element&.parent&.to_html
- エラーの詳細を取得
begin doc = Nokogiri::HTML(html) result = doc.css('selector').text rescue => e puts "エラータイプ: #{e.class}" puts "エラーメッセージ: #{e.message}" puts "バックトレース: #{e.backtrace.join("\n")}" end
これらの対処法を活用することで、多くの一般的なNokogiriのトラブルを解決できます。問題が発生した場合は、まずHTMLの構造とエンコーディングを確認し、その後で適切な対処法を選択することをお勧めします。
実践的なユースケース集
ニュースサイトの記事情報取得
- ニュース記事スクレイパーの実装
class NewsArticleScraper def initialize @headers = { 'User-Agent' => 'NewsBot/1.0 (contact@example.com)' } end def scrape_article(url) doc = Nokogiri::HTML(URI.open(url, @headers)) { title: extract_title(doc), publish_date: extract_date(doc), author: extract_author(doc), content: extract_content(doc), categories: extract_categories(doc) } end private def extract_title(doc) # 一般的なニュースサイトのタイトル要素パターン doc.at_css('h1.article-title, .entry-title, [itemprop="headline"]')&.text&.strip end def extract_date(doc) # 日付の抽出と解析 date_text = doc.at_css('time, .date, [itemprop="datePublished"]')&.[]('datetime') || doc.at_css('time, .date, [itemprop="datePublished"]')&.text return nil unless date_text DateTime.parse(date_text) rescue nil end def extract_author(doc) doc.at_css('[itemprop="author"], .author-name, .writer')&.text&.strip end def extract_content(doc) # 記事本文の抽出(広告や関連記事を除外) main_content = doc.css('.article-body p, .entry-content p').map(&:text).join("\n\n") main_content.gsub(/\n{3,}/, "\n\n").strip end def extract_categories(doc) doc.css('.category, .tags a').map(&:text).map(&:strip).uniq end end # 使用例 scraper = NewsArticleScraper.new article = scraper.scrape_article('https://example.com/news/123')
Eコマースサイトの商品データ収集
- 商品情報スクレイパーの実装
class ProductScraper def initialize @rate_limiter = RateLimiter.new(requests_per_minute: 20) end def scrape_product_listing(url) @rate_limiter.throttle do doc = Nokogiri::HTML(URI.open(url)) products = doc.css('.product-item').map do |item| { name: extract_product_name(item), price: extract_price(item), availability: extract_availability(item), specifications: extract_specifications(item), image_url: extract_image_url(item) } end { products: products, next_page: extract_next_page(doc) } end end private def extract_product_name(item) item.at_css('.product-name, h2')&.text&.strip end def extract_price(item) price_text = item.at_css('.price, [itemprop="price"]')&.text&.strip return nil unless price_text # 価格のクリーニング price_text.gsub(/[^\d]/, '').to_i end def extract_availability(item) status = item.at_css('.stock-status, .availability')&.text&.strip case status when /在庫あり|在庫有り/ :in_stock when /残りわずか/ :limited_stock else :out_of_stock end end def extract_specifications(item) item.css('.specifications li, .specs tr').each_with_object({}) do |spec, hash| key = spec.at_css('.label, th')&.text&.strip value = spec.at_css('.value, td')&.text&.strip hash[key] = value if key && value end end def extract_image_url(item) item.at_css('img.product-image')&.[]('src') end def extract_next_page(doc) next_link = doc.at_css('.pagination .next a') next_link&.[]('href') end end
SNSプロフィール情報の抽出
- プロフィールスクレイパーの実装
class ProfileScraper def initialize @cache = {} end def scrape_profile(url) return @cache[url] if @cache[url] doc = Nokogiri::HTML(URI.open(url)) profile = { username: extract_username(doc), bio: extract_bio(doc), followers: extract_followers(doc), following: extract_following(doc), posts: extract_posts(doc), verified: is_verified?(doc) } @cache[url] = profile profile end private def extract_username(doc) doc.at_css('.profile-username, .user-name')&.text&.strip end def extract_bio(doc) doc.at_css('.bio, .profile-description')&.text&.strip end def extract_followers(doc) count_text = doc.at_css('.followers-count')&.text&.strip parse_count(count_text) end def extract_following(doc) count_text = doc.at_css('.following-count')&.text&.strip parse_count(count_text) end def extract_posts(doc) posts = doc.css('.post-item').map do |post| { content: post.at_css('.post-content')&.text&.strip, timestamp: parse_timestamp(post.at_css('.timestamp')&.text), likes: parse_count(post.at_css('.likes-count')&.text) } end posts.compact end def is_verified?(doc) !!doc.at_css('.verified-badge, .verified-icon') end private def parse_count(text) return 0 unless text case text.downcase when /k$/ (text.to_f * 1000).to_i when /m$/ (text.to_f * 1_000_000).to_i else text.gsub(/[^\d]/, '').to_i end end def parse_timestamp(text) return nil unless text begin case text when /(\d+)秒前/ Time.now - $1.to_i when /(\d+)分前/ Time.now - ($1.to_i * 60) when /(\d+)時間前/ Time.now - ($1.to_i * 3600) when /(\d+)日前/ Time.now - ($1.to_i * 86400) else Time.parse(text) end rescue nil end end end
これらの実装例は、実際のプロジェクトですぐに活用できる実践的なコードです。ただし、実際の使用時には以下の点に注意してください:
- サイトの利用規約とrobots.txtを必ず確認する
- 適切なレート制限を設定する
- エラー処理を実装する
- キャッシュ戦略を検討する
- 対象サイトの構造変更に対応できるように設計する
これらのコードは基本的な実装例であり、実際のプロジェクトでは要件に応じて適切にカスタマイズすることをお勧めします。