https://www.toutiao.com/article/7470490693815861775
C#中使用webview2来批量下载网页中的图片
2025-02-12 20:07·办公小技巧
看到某些网页上的图片,是不是有一种想全部保存下来的想法了,一张一张来保存显示不是我们的风格,也没有效率,那么有没有一个工具可以批量把网页上显示的图片都保存下来,并且还能过滤掉小图片、广告图片了?
虽然有各种插件可以实现保存网页上的图片,但是还是想写一个适合自己用的,输入一个网页,然后把符合条件的图片保存到本地来,这里有两种方法,一种是把网页显示出来,另一种不显示网页,直接提取网页源码中的图片地址,在这里主要是讲一种怎么实现。
最终的效果
需要实现的功能
1、加载显示网页使用C#中的webview2来实现
2、过滤图片通过图片的宽度和高度来实现,只有宽度和高度都符合的图片才进行提取
3、可以实现图片的批量下载,并根据网页的标题保存到指定的文件夹中
4、大部分网页上的图片可以保存,有些网页图片可以在浏览器中查看,但是在软件中要下载时要么需要验证,要么需要Cookie,要么图片地址的格式很奇怪,就无法下载。
界面实现
如果想界面美观一些,可以使用antdui,vs2022中本身也没有webview2,需要negut来下载一下,网页在webview2中加载完成显示出来后,提取其中的图片保存到DataGridView中,方便后面进行批量下载。
界面设计
图片大小的过滤
在webview2中,可以使用ExecuteScriptAsync来执行js脚本,在脚本中就可以获取图片的宽和高,然后进行过滤,需要先定义一下图片过滤的参数和类
public static int imgW = 300;
public static int imgH = 200;
public static string imgSavePath = "D:\\ImgDown";
public static string Title = "";
public static string imgUrl = "";
// 定义图片信息的类
public class ImageInfo
{
[JsonProperty("src")]
public string Src { get; set; }
[JsonProperty("width")]
public int Width { get; set; }
[JsonProperty("height")]
public int Height { get; set; }
}
public class ImageDown
{
public string url { get; set; }
public int index { get; set; }
}
可以自行设计个界面来设置过滤图片的宽、高、默认保存路径等参数。
再新建一个类文件,就叫imghelp.cs吧,用来存放一些小东西,方便使用,如验证url的、获取扩展名的、正则提取标题的等。
/// <summary>
/// 获取扩展名
/// </summary>
/// <param name="contentType"></param>
/// <returns></returns>
public static string GetExtType(string contentType)
{
switch (contentType)
{
case "image/jpeg":
return "jpg";
case "image/png":
return "png";
case "image/gif":
return "gif";
case "image/bmp":
return "bmp";
case "image/webp":
return "webp";
default:
return string.Empty; // 未知类型
}
}
/// <summary>
/// 正则表达式判断是否是一个网址
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static bool IsValidUrl(string input)
{
// 正则表达式匹配常见的网址格式
string pattern = @"^(https?|ftp):\/\/([^\s\/$.?#].[^\s]*)$";
return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
}
/// <summary>
/// 正则表达式:匹配中文字符
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static MatchCollection MatchChineseCharacters(string input)
{
// 正则表达式:匹配中文字符
string pattern = @"[\u4e00-\u9fa5]";
return Regex.Matches(input, pattern);
}
/// <summary>
/// 提取标题,去除字符串中的符号、括号内内容,返回长度不超过10个字符的标题
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string GetTitle(string input)
{
StringBuilder sb = new StringBuilder();
bool inBracket = false; // 标记是否在括号内
foreach (char c in input)
{
if (c == '(')
{
inBracket = true; // 进入括号内
}
else if (c == ')')
{
inBracket = false; // 离开括号内
}
else if (c == '【')
{
inBracket = true; // 进入括号内
}
else if (c == '】')
{
inBracket = false; // 离开括号内
}
else if (c == '(')
{
inBracket = true; // 进入括号内
}
else if (c == ')')
{
inBracket = false; // 离开括号内
}
else if (c == '[')
{
inBracket = true; // 进入括号内
}
else if (c == ']')
{
inBracket = false; // 离开括号内
}
else if (c == '-')
{
inBracket = true; // 进入括号内
}
else if (!inBracket)
{
sb.Append(c); // 仅在不在括号内时添加字符到结果中
}
}
string pattern = @"[^\u4e00-\u9fa5]";
string result = Regex.Replace(sb.ToString(), pattern, "");
if (result.Length > 10)
{
result = result.Substring(0, 10);
}
return result;
}
打开一个网址
这里打开的网址需要带http或https,不然无法使用,可以使用IsValidUrl来验证一下是否符合要求,然后等着网页打开就可以了,处理一下按钮和表。
//打开一个新网址
DG_show.Rows.Clear();
btn_down.Enabled= false;
string url = txt_url.Text.Trim().ToString();
if (ImgHelp.IsValidUrl(url))
{
alert1.Text = "打开网页,图片获取中...";
web2.Source = new Uri(url);
}
else
{
MessageBox.Show("输入的似乎不是一个正确的网址,需要包含http部分!", "错误提醒!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
那怎么知道网页已经加载完成了呢,咱们看到网页显示出来不算数,得通过这个事件来判断
private void web2_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
if (e.IsSuccess)
{
//开始采集图片
CaptureImages();
}
}
如果网页加载成功了,完全显示出来了,就开始通过CaptureImages提取图片吧
/// <summary>
/// 网页加载完成后,采集图片
/// </summary>
private async void CaptureImages()
{
await web2.EnsureCoreWebView2Async(); // 确保WebView2已初始化并加载核心组件。
string script = @"
var images = document.querySelectorAll('img');
var imageData = [];
images.forEach(img => {
imageData.push({
src: img.src,
width: img.width,
height: img.height
});
});
imageData;
";
var result = await web2.CoreWebView2.ExecuteScriptAsync(script);
// 解析返回的JSON字符串
var imageData = JsonConvert.DeserializeObject<List<ImageInfo>>(result);
// 输出图片地址
DG_show.Rows.Clear();
foreach (var image in imageData)
{
if (image.Width >= imgW && image.Height >= imgH)
{
DG_show.Rows.Add(image.Src, image.Width, image.Height);
}
}
int num = imageData.Count;
int num1 = DG_show.Rows.Count;
alert1.Text = $"获取完成,本次共获取到{num}张图片,过滤后还剩余{num1}张图片!";
Title =ImgHelp.GetTitle(web2.CoreWebView2.DocumentTitle);
this.Text = Title;
btn_down.Enabled = true;
}
其中最关键的是script这地方,能不能提取到图片的地址全靠它了,提取完成后判断一下,符合条件的放到表格中,不符合的直接不管了。
下载图片
下载图片本以为很简单,但实现起来很是头大,并不能直接把webview2中显示的图片直接保存出来,是不是很心塞,网上有例子说通过ExecuteScriptAsync来执行脚本,可以把显示的图片重画一下,然后保存,但是试了好久都不成功,直接放弃了。
然后就是通过HttpClient来保存图片了,现在好些图片要么需要模拟自己是浏览器打开的,要么需要带有cookie验证,反正能直接打开的很少,还有一些图片路径很反人类,也读取不出来,所以不要看图片显示出来了,能保存出来的也只有一部分
保存图片当然要使用多线程了,不然一个一个的下载也没效率,使用tasks来实现多线程,并等待所有的线程下载完成。
//下载图片
btn_down.Enabled = false;
int num = DG_show.Rows.Count;
if (num < 1)
{
MessageBox.Show("表格中没有需要下载的图片内容!", "错误提醒!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
////启用线程池,进行多线程下载
string url = "";
Task[] tasks = new Task[num];
for (int i = 0; i < num; i++)
{
ImageDown imgdown = new ImageDown();
url = DG_show.Rows[i].Cells["图片地址"].Value.ToString();
imgdown.url = url;
imgdown.index = i;
tasks[i] = Task.Run(() => DownloadImageAsync(imgdown));
}
await Task.WhenAll(tasks);
MessageBox.Show(“图片已经下载完成,默认保存在D盘根目录下!”, “完成提醒!”, MessageBoxButtons.OK, MessageBoxIcon.Warning);
btn_down.Enabled = true;
DownloadImageAsync下载图片,需要注意的是文件名的生成,如果想要使用图片原来的名称,有些文件名很奇怪,会导致保存失败,这里获取一下扩展名,然后直接按在表格中的顺序来命名图片了。
private async Task DownloadImageAsync(ImageDown imageDown)
{
string imageUrl = imageDown.url;
int index = imageDown.index;
string img_path = imgSavePath.Trim() + "\\" + Title.Trim();
if (!Directory.Exists(img_path))
{
Directory.CreateDirectory(img_path);
}
try
{
if (imageUrl.StartsWith("data:image"))
{
}
else
{
// 图片下载,增加头文件
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
client.DefaultRequestHeaders.Add("Accept", "image/webp,image/apng,image/*,*/*;q=0.8");
client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.9");
//获取扩展名
var headResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, imageUrl));
string contentType = headResponse.Content.Headers.ContentType.MediaType;
string ext =ImgHelp.GetExtType(contentType);
byte[] imageData = await client.GetByteArrayAsync(imageUrl);
HttpResponseMessage response = await client.GetAsync(imageUrl);
response.EnsureSuccessStatusCode(); // 确保请求成功
//string fileName = Path.GetFileName(new Uri(imageUrl).LocalPath).Trim();
string fileName =index.ToString()+"."+ext;
string savePath = Path.Combine(img_path, fileName).Trim();
using (FileStream fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
await response.Content.CopyToAsync(fileStream);
}
updateGridView(index, Color.GreenYellow); //更新DataGridView
}
}
}
catch (Exception ex)
{
updateGridView(index, Color.Red);//更新DataGridView
Console.WriteLine(ex.Message.ToString());
}
}
在线程中更新表格的UI,也就是下载成功的加个绿色,失败的加个红色
private void updateGridView(int i, Color c)
{
if (DG_show.InvokeRequired)
{
DG_show.BeginInvoke(new Action<int, Color>(updateGridView), i, c);
}
else
{
DG_show.Rows[i].DefaultCellStyle.BackColor = c;
}
}
完成
到此,基本上就已经完成了,可以输入几个网址试一下运行的效果。
这种方法保存图片有个缺点,就是有些网页的显示速度比较慢,还有同一个网址在第二次打开时,无法获取到图片的地址,这算是个bug吧,应该是这一句导致的,但去掉又不合适。
await web2.EnsureCoreWebView2Async(); // 确保WebView2已初始化并加载核心组件。