- 1. 概要
- 2. メイン
- 3. リンク URI を抽出する
- 4. リンク先をチェックする
- 5. 分類してチェック
- 6. # をチェック
- 7. ./ や ../ の編集
- 8. リンク先を取得
- 9. 現時点での問題点
1. 概要
前々項までのものは1ページ分のチェックしかできませんでしたが、今回は、ディレクトリの位置が同列のものまでは、再帰的にチェックを行うようにしました。
まぁ、ここまで行けばわたしの思うところは十分に満たせるので・・・。
後は動かしながら、不具合を修正していきたいと思います。
まだまだ、手直しが入ると思いますので・・・。
2. メイン
前項のコンフィグレーションファイルの読込を加えました。
#!/usr/bin/env ruby
=begin
v0.1 リンク切れチェックを行う、下記の形式で起動
./linkcheck.rb URI
v0.2 チェック済の結果を保持する処理に誤りがあったので修正
=end
require('./readconf')
require('./checkparent')
tree = { ARGV[0] => 'UNKNOWN' }
checked = {}
$checkID = []
$nocheck = []
readconf = ReadConf.new
readconf.execute()
parent = CheckParent.new(ARGV[0])
parent.execute(ARGV[0], tree, checked)
3. リンク URI を抽出する
リンク先 URI を抽出する部分。
これも大きな変更はありません。
例外処理を少し加えただけです。
require './checkchild'
require 'open-uri'
require 'nokogiri'
class CheckParent
def initialize(original)
@check = Check.new(original)
end
def execute(uri, tree, checked)
begin
tree.each do | key, value |
if (key == uri)
if (value != 'UNKNOWN')
return;
end
end
end
tree[uri] = 'CHECKING'
originalURI = uri
now = Time::now()
nowtime = sprintf("%02d",now.hour)+":"+sprintf("%02d",now.min)+":"+sprintf("%02d",now.sec)
print("#{nowtime} 検索 [#{originalURI}] ")
uri = URI.encode(uri)
uri = URI.parse(uri)
print(".")
html = open(uri).read
html.sub!(/^<!DOCTYPE html(.*)$/, '<!DOCTYPE html>')
doc = Nokogiri::HTML(html)
link = {}
doc.css('a').each do | tag |
link[tag[:href]] = nil
end
doc.css('input').each do | tag |
if s = tag[:onclick]
s = s[/\'.*\'/]
link[s.delete!("'")] = nil
end
end
doc.css('img').each do | tag |
link[tag[:src]] = nil
end
link = Hash[link.sort]
@check.execute(originalURI, doc, link, tree, checked)
rescue OpenURI::HTTPError => error
puts(" #{uri} ")
puts("例外発生[#{error.class}]")
return;
rescue => error
puts "例外発生[#{error.class}]"
puts "#{error.backtrace}"
puts "[#{self.class.name}][#{error.message}]"
exit 1
end
end
end
4. リンク先をチェックする
リンク先をチェックする部分。
ここが一番変更の多かった部分です。
require('pry')
require('./classific')
class Check
def initialize(original)
protocol, userinfo, domain, other = URI.split(ARGV[0])
@top = "#{protocol}" + "://" + "#{domain}"
end
def execute(parent, doc, child, tree, checked)
# parent の URI の末尾を加工してインデックスまでを求める
index = parent
if (index[-1, 1] != '/') # 末尾が / でない場合は 末尾のひとつ前の / までをインデックスファイルとする
split = parent.split('/')
last = split.last
if (last.include?('.'))
index = parent[0,parent.length-last.length]
end
end
classific = Classific.new
classific.execute(parent, @top, doc, index, child, tree, checked)
puts("")
child.each_value do | value |
if ((value[:結果] != 'OK') &&
(value[:結果] != '未チェック') &&
(value[:結果] != 'ADV'))
puts(" #{value[:リンク]} ")
puts(" → #{value[:URI]}")
end
end
#return; # 再帰検索しない場合はここを生かす
child.each_value do | value |
if (value[:種別][0,2] != '内部') # 内部リンクでないと検索しない
next
end
if (value[:結果] != 'OK') # OK じゃないものは既にリンク切れなので検索しない
next
end
if (value[:リンク][0,2] == '..') # .. で上位へ行くものは探さない
next
end
if (value[:URI].include?('#')) # # を含んでいるものまでは検索しない
next
end
if (index != value[:URI][0, index.length]) # インデックス部分まで一致しないものは検索しない
next
end
parent = CheckParent.new(value[:URI])
parent.execute(value[:URI], tree, checked)
end
rescue => error
binding.pry
puts "例外発生[#{error.class}]"
puts "#{error.backtrace}"
puts "[#{self.class.name}][#{error.message}]"
exit 1
end
end
24、25行。
実際にリンク先を分類してチェックするのは別クラスに実装しました。
39~62 行で
他ドメインでない
既にリンク切れでない
上位のディレクトリではない
URI を再帰的に検索するようにしています。
5. 分類してチェック
前項でリンク先を分類してチェックする箇所を別に抜き出して処理しています。
=begin
リンクを分類してチェックする
=end
require('pry')
require('open-uri')
require('nokogiri')
require('./assemble')
require('./searchName')
require('./fetch')
class Classific
def initialize()
end
def execute(parent, top, doc, index, child, tree, checked)
search = SearchName.new
assemble = Assemble.new
fetch = Fetch.new
child.each_key do | key |
full = key
kind = " "
res = '未チェック'
case key[0]
when '#' # ページ内リンクなので html 内に id=key が存在すること
kind = "ページ内リンク "
res = search.execute(parent, doc, key)
when 'h' # 外部リンクなので存在の有無のみ
kind = "外部 リンク "
res, full = fetch.execute(parent, top, key, checked)
checked[full] = res
when '.' # 内部リンクなのでたどる
kind = "内部 . リンク "
res, full = fetch.execute(parent, top, assemble.execute(index, key), checked)
checked[full] = res
else
if ((key[-3, 3] == 'png') || # イメージなので存在の有無のみ
(key[-3, 3] == 'gif'))
kind = "イメージファイル "
res, full = fetch.execute(parent, top, key, checked)
checked[full] = res
elsif ((key[-4, 4] == 'html') || # 内部リンクなのでたどる
(key[-1, 1] == '/'))
kind = "内部 リンク "
res, full = fetch.execute(parent, top, key, checked)
checked[full] = res
else # 上記以外なのでチェックしない
kind = "その他のもの "
end
end
child[key] = { :リンク元 => parent, :種別 => kind, :リンク => key, :結果 => res, :URI => full }
end
rescue => error
binding.pry
puts "例外発生[#{error.class}]"
puts "#{error.backtrace}"
puts "[#{self.class.name}][#{error.message}]"
exit 1
end
end
6. # をチェック
# で始まるリンク先は、ドキュメント内に「id="リンク先"」の記述があれば OK とします。
「id="リンク先"」の記述を含むタグは前ページに掲載したコンフィグレーションファイルからの読込によって設定しています。
require('pry')
=begin
# で始まる要素はページ内の特定の場所へのリンク
指定したタグ内に id="" の形式で記述してあるものを探す
=end
class SearchName
def initialize()
end
# 引数としてドキュメント自身が必要
def execute(parent, doc, id)
begin
$checkID.each do | tag | # タグを検索
doc.css(tag).each do | attribute |
if (!attribute[:id]) # id がない場合はどうしようもない
next
elsif ("#" + attribute[:id] == id)
return 'OK' # 検出したら 'OK' を返す
end
end
end
rescue => error
puts("例外発生[#{self.class.name}]")
puts("#{parent}]内の[#{id}]検索中")
puts("例外[#{error.class}]")
puts("#{error.backtrace}")
puts("#{error.message}")
exit 1
end
return 'NG'
end
end
7. ./ や ../ の編集
「./」や「../」で始まるリンク先は、リンク元のドキュメントのルートやその上と組み合わせて、URI を再構築します。
require('pry')
=begin
./ や ../ で始まる URI を組み立て直す
=end
class Assemble
def initialize()
end
def execute(index, original)
uri = original;
begin
if (original == '../') # ../ のみであれば
split = index.split('/') # ひとつ上のインデックスとする
uri = index[0, index.length-(split.last.length+1)]
elsif (original[0, 2] == '..') # ../ インデックスのひとつ上に / 以降を加える
split = index.split('/')
uri = index[0, index.length-(split.last.length+2)] + original[2,original.length-2]
else # ./ インデックスに / 以降を加える
uri = index + original[2,original.length-2]
end
rescue => error
puts "例外発生[#{error.class}]"
puts "#{error.backtrace}"
puts "[#{self.class.name}][#{error.message}]"
exit 1
end
return uri
end
end
8. リンク先を取得
ドキュメント内の記述でないリンク先は取得してみます。
=begin
外部リンク・ドメイン内リンク・イメージファイルは open してみる
=end
class Fetch
def initialize()
end
def execute(parent, top, check, checked)
begin
if (check[0, 'https://px.a8.net/'.length] == 'https://px.a8.net/')
return 'ADV', check
end
original = check
if (check.include?('#'))
split = check.split('#')
sharp = split.last
check = check[0, check.length-(split.last.length+1)]
end
case check[0]
when ('/') # / で始まっているものはドメイン内リンクなのでドメインをつける
check = top + check
end
uri = URI.encode(check)
uri = URI.parse(uri)
# 既にチェック済であればその結果をそのまま返す
if (checked.has_key?(check))
return checked[check], check
end
print(".")
if (sharp)
html = open(uri).read
html.sub!(/^<!DOCTYPE html(.*)$/, '<!DOCTYPE html>')
doc = Nokogiri::HTML(html)
search = SearchName.new # ページ内リンクなので html 内に id=key が存在すること
response = search.execute(parent, doc, '#'+sharp)
if (response != 'OK')
File.open(sharp+".txt", "w") do | file |
file.puts(doc)
end
end
return response, check
else
open(uri)
end
rescue OpenURI::HTTPError => error
return 'NG', check
rescue SocketError => error
return 'SocketError', check
rescue OpenSSL::SSL::SSLError => error
binding.pry
return 'SSLError', check
rescue RuntimeError => error
if (error.message.include?('redirection forbidden'))
return 'redirection forbidden', check
else
puts "例外発生[#{error.class}]"
puts "#{error.backtrace}"
puts "[#{self.class.name}][#{error.message}]"
exit 1
end
rescue TypeError => error
if (error.message.include?('no implicit conversion'))
return error.message, check
else
puts "例外発生[#{error.class}]"
puts "#{error.backtrace}"
puts "[#{self.class.name}][#{error.message}]"
exit 1
end
rescue Errno::ENOENT => error # No such file or directory
return 'No such file or directory', check
rescue => error
binding.pry
puts "例外発生[#{error.class}]"
puts "#{error.backtrace}"
puts "[#{self.class.name}][#{error.message}]"
exit 1
end
return 'OK', check
end
end
9. 現時点での問題点
一応ある程度チェックできているように見えます。
チェック済の URI に関しては、前のチェック結果をそのまま返すようにして、同一 URI を複数回アクセスしないようにしているつもりですが、どうも動作を見ていると同じ URI に何度もアクセスしているように見えるので、調査中です。
本件、2018年6月8日に誤りがあったことがわかりましたので修正しました。
以降、これらの修正は「リンクチェック」をご参照ください。
|