我有一个包含大量 .html 文件的文件夹,这些文件都有<p>..</p>
html 标签,并且<img...>
所有文件都有 html 标签
我正在尝试提取仅 URL <img...>
在html里面找到将当前文件夹中的所有文件标记为一个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 路径中包含任何内容。
我的问题是,除了 html 标签内之外,其中一些 html 文件可能还有其他 URL,因此我只需要从<img ...>
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,无论它们在哪里找到:
sed -n 's#.*\(https*://[^"]*\).*#\1#;p' file
谢谢你!
答案1
你真的不想使用sed
或grep
或任何基于正则表达式的提取方法。 HTML 是结构化文本,因此您需要一个 HTML 解析器来可靠地从中提取数据。
大多数语言都有可用的 HTML 解析库,包括 C、go、rust、java、python、php、perl 等等。还有一些命令行工具,例如xml_grep
和xmlstarlet
用于在 shell 脚本中解析和处理 HTML/XHTML/XML 文件 - 它们很好,但根据我的经验,它们往往对要求输入文件符合规范更加严格。特别是对于 HTML,这可能会导致问题 - 严格遵守规范对于现实世界的网站来说并不常见(这是“HTML 文件通常是垃圾”的礼貌说法)。解析库往往对它们将处理的内容更加宽容,可以轻松处理会被更严格的工具拒绝的输入。
顺便说一句,还有一些工具,例如xml2
和html2
用于将结构化文本转换为面向行的格式,可以更轻松地使用 grep、sed、cut 等面向行的工具进行处理。
不管怎样,使用 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 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/
目录并将其添加到自己的脚本的 $PATH 中是相当常见的做法),您可以从任何地方运行它,无需指定路径,就像使用常见程序(如 find、grep、sed、awk、perl 等)一样。
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 编程、变量引用、数组、对象等
TL;博士:这很神奇。
顺便说一句,您可能想知道为什么脚本使用$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">'
]
这是一个索引数组,其中包含一个字符串'S'
(这意味着当前标记是一个开始标记,如<p>
...“E”表示一个结束标记,如</p>
)作为元素 0、一个包含 HTML 标记名称'img'
作为元素 1 的字符串、一个哈希值(关联数组,在 中{...}
)包含 HTML 标签的属性名称(键)和值作为元素 2,另一个索引数组或“列表”再次包含属性名称作为元素 4(在 中[...]
),以及 img src 标签的实际 html 文本作为元素 5。
这在 中进行了记录man HTML::TokeParser
,其中表示“S”类型令牌具有["S", $tag, $attr, $attrseq, $text]
。部分解释Argspec
了man HTML::Parser
为什么它包含一个散列 ($attr) 和一个包含散列键的数组 ($attrseq)。这是因为哈希本质上是无序的,并且数组用于记住img
HTML 源代码中的标签中看到的键的原始顺序。这是一种非常常见的技术,可以在不丢失键顺序的情况下获得哈希的便利。
在 perl 代码中,$token->[1]
指的是第二个元素(perl 数组从 0 开始,而不是 1),因此我们检查它是否是img
(不区分大小写)。如果是,则我们在:src
中打印哈希的键。$token->[2]
$token->[2]->{src}
它->
被称为“箭头运算符”,用于取消引用(访问)数据结构中的值,与 C 或 C++ 中的使用方式非常相似。
您可以通过阅读(lol =“lists-of-lists”,AKA arrays-of-arrays,它们是包含其他数组的数组)和(“Perl Data Structures Cookbook”)的手册页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 中。 Data::Dump
不是,但很容易安装在 Debian 等上,apt-get install libdata-dump-perl
或者使用cpan
.它们都很好,但我通常更喜欢Data::Dump
.
顺便说一句,你不能在bash
bash 支持数组和关联数组(又名“哈希”)中执行这样的复杂数据结构,但元素只能包含简单的标量值,例如单个字符串或数字。它们不能包含嵌套数组或哈希。 Bash(以及其他一些类似 bourne 的 shell)具有一些变量引用功能,但是如果您发现自己正在使用它们,那么您确实应该使用更好的语言(几乎是任何其他语言) - 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