<p>..</p>
すべてhtmlタグがあり、<img...>
すべてのファイルにhtmlタグを含む多くの.htmlファイルを含むフォルダがあります。
抽出しようとしています。URLのみ <img...>
HTMLで発見現在のフォルダ内のすべてのファイルを1つのoutput.txt
ファイルとして表示
htmlファイルコードは次のとおりです。
file1.html
<p> text...</p>
<img style='display' src="https://www.example.com/image1.jpg" width="100px" alt="image1"/>
<p> text...</p>
<p> text.. <a href="https://www.example.com/1">1</a> ... text....</p>
file2.html
<p> text...</p>
<img style='display' src="https://www.example.com/image2.jpg" width="100px" alt="image2"/>
<p> text...</p>
<p> text.. <a href="https://www.example.com/2">2</a> ... text....</p>
イメージ URL パスには特定のパターンがなく、URL パスに何でも含めることができます。
<img ...>
私の問題は、これらのhtmlファイルの一部にhtmlタグの内側に他のURLがある可能性があることです。したがって、htmlタグから抽出するだけです。
<img ...>
理想的には、htmlタグ内で見つかったすべてのURLを次のようにoutput.txtに抽出しようとします。
output.txt
https://www.example.com/image1.jpg
https://www.example.com/image2.jpg
etc
sed または Regex を使用してこれを達成できますか?
私はこのsedコマンドを試しましたが、URLがどこにいてもすべてのURLを抽出するようです。
sed -n 's#.*\(https*://[^"]*\).*#\1#;p' file
ありがとうございます!
答え1
あなた本物正規表現ベースの抽出方法をsed
使用したくありません。grep
HTMLは構造化されたテキストなので、そのテキストからデータを確実に抽出するにはHTMLパーサーが必要です。
C、go、Rust、java、python、php、perlなどを含むほとんどの言語に使用できるHTML解析ライブラリがあります。シェルスクリプトでHTML / XHTML / XMLファイルを解析して処理するためのコマンドラインツールもあります。xml_grep
このxmlstarlet
ツールは素晴らしいですが、私の経験では、入力ファイルが仕様に準拠するように要求する方が厳しい傾向があります。特にHTMLの場合、これは問題を引き起こす可能性があります。仕様に厳密に従うことは実際のウェブサイトでは一般的ではありません(「HTMLファイルは一般的にゴミです」と丁寧に言う方法です)。解析ライブラリは、処理する内容に対してより寛大な傾向があり、より厳しいツールで拒否された入力を簡単に処理できます。
ところで、構造化されたテキストを、grep、sed、cutなどの行ベースのツールを使用してより簡単に処理できる行ベースの形式に変換するツールもxml2
あります。html2
それにもかかわらず、HTMLパーサーを使用することは正規表現を使用するよりも安定しているだけでなく、一般的に簡単です。
Perlを使った例は次のとおりです。HTML::TokeParser::シンプルパーサー、シンプルなインターフェースHTML::パーサー基準寸法。一般的なLinuxディストリビューションを実行している場合は、ほぼ確実にパッケージ化されています。たとえば、Debian とその派生バージョンでは、libhtml-tokeparser-simple-perl
とlibhtml-parser-perl
。それ以外の場合は使用できますcpan
。
$ cat extract-img-urls.pl
#!/usr/bin/perl
use strict;
use v5.16; # for fc (fold case) function
use HTML::TokeParser::Simple;
foreach my $f (@ARGV) {
my $p = HTML::TokeParser::Simple->new(file => $f);
while (my $token = $p->get_token) {
next unless fc($token->[1]) eq fc('img');
print $token->[2]->{src} . "\n";
}
};
ファイルに保存し、chmodを使用して実行可能にします。例:chmod +x ./extract-img-urls.pl
引数として扱うHTMLファイルのリストを使用して実行します。これを手動で実行したり、find
などを使用して-exec
ファイル名のリストを提供したりできます。たとえば、以下は、IMG SRC URL が 2 つだけで、両方がindex.html
相対的であることを示しています。
$ find ~/public_html/ -maxdepth 1 -type f -name 'index.html' -exec ./extract-img-urls.pl {} +
cas.jpg
valid-html401.png
明らかに、find
単一のディレクトリ内の単一のファイルを一致させるために使用するのは過剰です。これはうまくいきますが..../extract-img-urls.pl ~/public_html/index.html
複数のサブディレクトリにある複数のファイルの良い例ではありません。
-name '*.html'
あなたの場合(または-iname '*.html'
大文字と小文字を区別しない一致)を使用して実行できます。-maxdepth 1
サブディレクトリでも.htmlファイルを見つけるために述語を削除することもできます。
find ~/public_html/ -type f -iname '*.html' -exec ./extract-img-urls.pl {} +
最後に、この例では、Perlスクリプトが現在のディレクトリにあると想定しています。そうでない場合は、代わりに実際のパスを指定してください./
。または$ PATHのどこかにある場合(たとえば、ディレクトリ/usr/local/bin/
を作成する~/bin/
IMG SRC URL は通常相対 URL です。dirname()
次の関数を使用して(非常に簡単に)各相対イメージファイル名のデフォルトディレクトリを追加します。ファイル::デフォルト名モジュール(Perlに含まれるコアPerlモジュール):
#!/usr/bin/perl
use strict;
use v5.16; # for fc (fold case) function
use HTML::TokeParser::Simple;
use File::Basename;
foreach my $f (@ARGV) {
my $base = dirname($f);
my $p = HTML::TokeParser::Simple->new(file => $f);
while (my $token = $p->get_token) {
next unless fc($token->[1]) eq fc('img');
if ($token->[2]->{src} =~ m=^(https?|ftp)://|^/=i) {
print $token->[2]->{src} . "\n";
} else {
print $base . "/" . $token->[2]->{src} . "\n";
}
}
};
出力:
$ find ~/public_html/ -maxdepth 1 -type f -name 'index.html' -exec ./extract-img-urls.pl {} +
/home/cas/public_html/cas.jpg
/home/cas/public_html/valid-html401.png
最後に、このバージョンは処理する各ファイルの名前を印刷し、各IMG SRC URLの前にタブ文字を挿入し、\n
各ファイルの後に改行()を挿入します。他のツールやスクリプトを使用して出力を処理する場合は、改行区切り文字が便利です。多くのツール/言語には、「段落モード」でデータを読み取るオプションがあるため、これらのテキストを処理するのは簡単です。
#!/usr/bin/perl
use strict;
use v5.16; # for fc (fold case) function
use HTML::TokeParser::Simple;
use File::Basename;
foreach my $f (@ARGV) {
print "$f\n";
my $base = dirname($f);
my $p = HTML::TokeParser::Simple->new(file => $f);
while (my $token = $p->get_token) {
next unless fc($token->[1]) eq fc('img');
if ($token->[2]->{src} =~ m=^(https?|ftp)://|^/=i) {
print "\t" . $token->[2]->{src} . "\n";
} else {
print "\t" . $base . "/" . $token->[2]->{src} . "\n";
}
};
print "\n";
};
以下の出力例では、各段落の最初の行はファイル名で、残りの行はタブがプレフィックスで付けられたIMG SRC URLです。タブは主に人が読みやすいようにするためのものですが、ファイル名に改行文字が含まれている場合にも便利です。ファイル名に改行文字とタブが含まれている場合でも問題が発生する可能性があるため、完璧ではありません。したがって、通常、NUL文字をファイル名の区切り文字として使用することをお勧めします。これはファイル内の唯一の文字です。パス/ファイル名に無効な文字が含まれています。 )
$ ./extract-img-urls2.pl ~/public_html/index*.html
/home/cas/public_html/index.html
/home/cas/public_html/cas.jpg
/home/cas/public_html/valid-html401.png
/home/cas/public_html/index.old.html
/home/cas/public_html/cas.jpg
Perl プログラミング、変数参照、配列、オブジェクトなど
要約:本当に素晴らしいです。
しかし、なぜあなたはスクリプトでやの$token->[1]
ような変数を使うのか疑問に思います$token->[2]->{src}
。その理由は、メソッドから返されたオブジェクトの構造を確認しました。get_token
オブジェクトHTML::TokeParser::Simple::Token::Tag::Start
のデータ構造が次のようになっているからです。
[
'S',
'img',
{ 'src' => 'valid-html401.png',
'width' => '88',
'alt' => 'Valid HTML 4.01 Transitional',
'height' => '31'
},
[ 'src',
'alt',
'height',
'width'
],
'<img src="valid-html401.png" alt="Valid HTML 4.01 Transitional" height="31" width="88">'
]
これは、要素0の文字列'S'
(現在のタグが開始タグであることを意味します<p>
... "E"は終了タグを意味します)、要素1の文字列として</p>
HTMLタグ名を含む文字、属性名(キー)、およびHTMLタグ要素2として値を含むハッシュ'img'
(連想配列、in)、要素4(in)で属性名を再度含む別のインデックス配列、または「リスト」、およびimgの実際のHTMLテキストsrcタグを要素5として使用します。{...}
[...]
man HTML::TokeParser
これについての説明は、「S」タイプトークンにあると指定されています["S", $tag, $attr, $attrseq, $text]
。ハッシュ($attr)とハッシュキー($attrseq)を含む配列が含まれるArgspec
理由を部分的に説明します。これは、ハッシュが本質的に順序がなく、HTMLソースコードのタグに表示されているキーの元の順序をman HTML::Parser
記憶するために配列が使用されるためです。img
これはキーの順序を失うことなくハッシュの利便性を得る非常に一般的な技術です。
Perlコードは$token->[1]
2番目の要素(perl配列は1ではなく0から始まります)を参照しているため、その要素が大文字と小文字を区別していることを確認img
してください。その場合、src
ハッシュのキーを:に印刷します。$token->[2]
$token->[2]->{src}
「矢印演算子」と呼ばれ、->
C や C++ で使用される方法と同様に、データ構造の値を逆参照 (アクセス) するために使用されます。
(lol="lists-of-lists", AKA arrays-of-arrays (他の配列を含む配列) および ("Perl データ構造クックブック") 情報) のマニュアルページを読んでperldata
Perl データの詳細を見ることができます。 。チュートリアルもご覧ください。perllol
perldsc
man perlref
man perlreftut
矢印演算子は、Perlオブジェクト指向プログラミングでメソッド(サブルーチンなど)を呼び出すためにも使用されます(参照man perlobj
)$p = HTML::TokeParser::Simple->new(...)
。$p
HTML::TokeParser::Simple
$p->get_token
$p
get_token()
$token
次のモジュールを使用できます。データ::ダンプまたはデータ::ダンパーこのようなコードを開発/デバッグするときは、データ構造とオブジェクトをきれいに印刷します。文書を検索するよりも高速で作業量が少ないことがよくあります。
Data:Dumper
Perlに含まれるコアモジュールです。いいえ、しかしDebianなどにインストールしたり、使いData::Dump
やすいです。どちらも良いですが、一般的に。apt-get install libdata-dump-perl
cpan
Data::Dump
ちなみに、bash
配列と連想配列(別名「ハッシュ」)をサポートするbashでは、これらの複雑なデータ構造を実行できませんが、要素には単一の文字列や数字などの単純なスカラー値のみを含めることができます。入れ子になった配列やハッシュを含めることはできません。 Bash(および他のbourneに似たシェル)にはいくつかの変数引用がありますが、それを使用している場合は、より良い言語(ほぼ他の言語)を使用する必要があります。 bashはデータ処理には適していません。他のプログラム(grep、sed、cut、perl、awkなど)の実行を設定および調整するために使用される言語。
答え2
使用sed
$ sed -n '/<img/s/.*src=\([^ ]*\).*/\1/p' file1 file2
https://www.example.com/image1.jpg
https://www.example.com/image2.jpg
答え3
これはXMLパーサーを使用するソリューションです。
for file in *.html
do
xmlstarlet format --html "$file" 2>/dev/null |
xmlstarlet select --template --value-of '//img/@src' --nl
done >output.txt
HTMLを正しい形式のXMLに強制変換してから、各src
タグから属性値を順番に選択します。<img/>
完全な結果セットの作成output.txt
HTMLファイルがすでに正しい形式のXMLである場合は、明示的なループを省略し、シーケンス全体を単一のコマンドに減らすことができます。
xmlstarlet select --template --value-of '//img/@src' --nl *.html >output.txt
またはあまり冗長ではありません
xmlstarlet sel -t -v '//img/@src' -n *.html >output.txt
答え4
これは働きます:
grep '^<img' *.html | grep -o 'http[^"]*' > output.txt