From 09c7ea9b32260faafa7033e6c28990885b65b04a Mon Sep 17 00:00:00 2001 From: Vincent Wang <376787823@qq.com> Date: Mon, 14 Oct 2024 00:09:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=20=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=8E=A8=E8=8D=90=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=EF=BC=81=EF=BC=81=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- WeiboAlbumDownloader/Enums/WeiboDataSource.cs | 13 +- WeiboAlbumDownloader/Helpers/HttpHelper.cs | 24 + WeiboAlbumDownloader/MainWindow.xaml.cs | 393 ++++++++++++-- .../Models/AlbumDetailModel2.cs | 22 +- WeiboAlbumDownloader/Models/SettingsModel.cs | 8 +- .../Models/WeiboCnMobileModel.cs | 500 ++++++++++++++++++ 7 files changed, 887 insertions(+), 75 deletions(-) create mode 100644 WeiboAlbumDownloader/Models/WeiboCnMobileModel.cs diff --git a/README.md b/README.md index f7a65fa..be74ada 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ PC打开[weibo.com](https://weibo.com/),点击某一用户头像,进入主 -按F12进入控制台,网络-全部,在名称栏选择uid,标头-请求标头-Cookie。右键复制后请填入到Seetings.json。 +Cookie可以通过点击上方按钮打开页面扫码获取,或者按F12进入控制台,网络-全部,在名称栏选择uid,标头-请求标头-Cookie。右键复制后请填入到Seetings.json。 diff --git a/WeiboAlbumDownloader/Enums/WeiboDataSource.cs b/WeiboAlbumDownloader/Enums/WeiboDataSource.cs index abe376b..9cdfc3f 100644 --- a/WeiboAlbumDownloader/Enums/WeiboDataSource.cs +++ b/WeiboAlbumDownloader/Enums/WeiboDataSource.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace WeiboAlbumDownloader.Enums +namespace WeiboAlbumDownloader.Enums { public enum WeiboDataSource { WeiboCn = 0, - WeiboCom1 = 1, - WeiboCom2 = 2, + WeiboCnMobile = 1, + WeiboCom1 = 2, + WeiboCom2 = 3, } } diff --git a/WeiboAlbumDownloader/Helpers/HttpHelper.cs b/WeiboAlbumDownloader/Helpers/HttpHelper.cs index 59e77fb..8ec2b3b 100644 --- a/WeiboAlbumDownloader/Helpers/HttpHelper.cs +++ b/WeiboAlbumDownloader/Helpers/HttpHelper.cs @@ -51,6 +51,7 @@ public static async Task GetAsync(string url, WeiboDataSource dataSource, { var stream = lxResponse.GetResponseStream(); StreamReader reader = new StreamReader(stream); + if (!string.IsNullOrEmpty(fileName)) { FileStream lxFS = File.Create(fileName); @@ -73,5 +74,28 @@ public static async Task GetAsync(string url, WeiboDataSource dataSource, throw; } } + + public static string GetUniqueFileName(string filePath) + { + // 获取文件目录和扩展名 + string directory = Path.GetDirectoryName(filePath)!; + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); + string extension = Path.GetExtension(filePath); + + int count = 1; + + // 初始文件路径 + string uniqueFilePath = filePath; + + // 检查文件是否存在,如果存在则循环添加编号直到找到一个不存在的文件名 + while (File.Exists(uniqueFilePath)) + { + string newFileName = $"{fileNameWithoutExtension}({count}){extension}"; + uniqueFilePath = Path.Combine(directory, newFileName); + count++; + } + + return uniqueFilePath; + } } } diff --git a/WeiboAlbumDownloader/MainWindow.xaml.cs b/WeiboAlbumDownloader/MainWindow.xaml.cs index 04904bf..281fbf1 100644 --- a/WeiboAlbumDownloader/MainWindow.xaml.cs +++ b/WeiboAlbumDownloader/MainWindow.xaml.cs @@ -10,7 +10,6 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Runtime.ConstrainedExecution; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -19,6 +18,7 @@ using WeiboAlbumDownloader.Enums; using WeiboAlbumDownloader.Helpers; using WeiboAlbumDownloader.Models; +using System.Windows.Controls; namespace WeiboAlbumDownloader { @@ -27,13 +27,17 @@ namespace WeiboAlbumDownloader /// public partial class MainWindow : MicaWindow { - - double currentVersion = 2.0; - + //发行说明: + //①此处升级一下版本号 + //②Github release新建一个新版本Tag + //③上传压缩包删除Settings.json以及uidList.txt + double currentVersion = 3.0; + /// /// com1是根据uid获取相册id,https://photo.weibo.com/albums/get_all?uid=10000000000&page=1;根据uid和相册id以及相册type获取图片列表,https://photo.weibo.com/photos/get_all?uid=10000000000&album_id=3959362334782071&page=1&type=3 /// com2是根据uid获取相册id,https://weibo.com/ajax/profile/getImageWall?uid=10000000000&sinceid=0&has_album=true;根据相册id和sinceid获取图片列表,https://weibo.com/ajax/profile/getAlbumDetail?containerid=123456789000123456_-_pc_profile_album_-_photo_-_camera_-_0_-_%25E5%258E%259F%25E5%2588%259B&since_id=0 /// cn是从 https://weibo.cn/10000000000/profile?page=2获取html解析发布的微博 + /// m.cn是移动端api,可以从 https://m.weibo.cn/api/container/getIndex?type=uid&value=10000000000&containerid=10760310000000000&since_id=5040866311798795,since_id第一次为空,containerid以107603开头是获取时间线,100505开头是个人资料 /// private WeiboDataSource dataSource = WeiboDataSource.WeiboCn; private int countPerPage = 100; @@ -57,7 +61,7 @@ public MainWindow() Directory.CreateDirectory(downloadFolder); ComboBox_DataSource.ItemsSource = Enum.GetNames(typeof(WeiboDataSource)); - ComboBox_DataSource.SelectedIndex = 0; + ComboBox_DataSource.SelectedIndex = 1; ListView_Messages.ItemsSource = Messages; //定时任务 @@ -76,16 +80,6 @@ public MainWindow() } } - private async void StartDownLoad(object sender, RoutedEventArgs e) - { - await Start(); - } - - private void StopDownLoad(object sender, RoutedEventArgs e) - { - cancellationTokenSource?.Cancel(); - } - private async Task GetVersion() { AppendLog($"当前程序版本V{currentVersion}"); @@ -146,11 +140,11 @@ await Task.Run(async () => TextBlock_UID.Text = userId; }); - var albums = await HttpHelper.GetAsync(albumsUrl, dataSource, cookie); + var albums = await HttpHelper.GetAsync(albumsUrl, dataSource, cookie!); Directory.CreateDirectory(downloadFolder + userId); if (albums != null) { - foreach (var item in albums?.data?.album_list) + foreach (var item in albums?.data?.album_list!) { if (isSkip) break; @@ -169,12 +163,12 @@ await Task.Run(async () => break; string albumUrl = $"https://photo.weibo.com/photos/get_all?uid={userId}&album_id={item.album_id}&count={countPerPage}&page={page}&type={item.type}"; - var photos = await HttpHelper.GetAsync(albumUrl, dataSource, cookie); + var photos = await HttpHelper.GetAsync(albumUrl, dataSource, cookie!); if (photos != null && photos.data?.photo_list != null && photos.data?.photo_list.Count > 0) { int photoCount = 1; string oldCaption = ""; - foreach (var photo in photos.data?.photo_list) + foreach (var photo in photos.data?.photo_list!) { if (cancellationTokenSource.IsCancellationRequested) { @@ -212,7 +206,7 @@ await Task.Run(async () => //传入图片/视频的名字,开始下载图片/视频 try { - await HttpHelper.GetAsync(photoUrl, dataSource, cookie, fileName); + await HttpHelper.GetAsync(photoUrl, dataSource, cookie!, fileName); AppendLog("已完成 " + Path.GetFileName(fileName), MessageEnum.Success); @@ -232,7 +226,7 @@ await Task.Run(async () => } //已存在的文件超过设置值,判定该用户下载过了 - if (settings.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) + if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) { AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info); isSkip = true; @@ -258,11 +252,11 @@ await Task.Run(async () => TextBlock_UID.Text = userId; }); - var albums = await HttpHelper.GetAsync(albumsUrl, dataSource, cookie); + var albums = await HttpHelper.GetAsync(albumsUrl, dataSource, cookie!); Directory.CreateDirectory(downloadFolder + userId); if (albums != null) { - foreach (var item in albums?.Data?.AlbumList) + foreach (var item in albums?.Data?.AlbumList!) { if (isSkip) break; @@ -281,11 +275,11 @@ await Task.Run(async () => break; string albumUrl = $"https://weibo.com/ajax/profile/getAlbumDetail?containerid={item.Containerid}&since_id={sinceId}"; - var photos = await HttpHelper.GetAsync(albumUrl, dataSource, cookie); + var photos = await HttpHelper.GetAsync(albumUrl, dataSource, cookie!); if (photos != null && photos.PhotoListData2?.PhotoListItem2 != null && photos.PhotoListData2?.PhotoListItem2.Count > 0) { sinceId = photos.PhotoListData2.SinceId; - foreach (var photo in photos.PhotoListData2?.PhotoListItem2) + foreach (var photo in photos.PhotoListData2?.PhotoListItem2!) { if (cancellationTokenSource.IsCancellationRequested) { @@ -308,7 +302,7 @@ await Task.Run(async () => //传入图片/视频的名字,开始下载图片/视频 try { - await HttpHelper.GetAsync(photoUrl, dataSource, cookie, fileName); + await HttpHelper.GetAsync(photoUrl, dataSource, cookie!, fileName); AppendLog("已完成 " + Path.GetFileName(fileName), MessageEnum.Success); } @@ -324,7 +318,7 @@ await Task.Run(async () => } //已存在的文件超过设置值,判定该用户下载过了 - if (settings.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) + if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) { AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info); isSkip = true; @@ -357,7 +351,7 @@ await Task.Run(async () => //https://weibo.cn/xxxxxxxxxxxxx?page=2 //https://weibo.cn/xxxxxxxxxxxxx/profile?page=2 string url = $"https://weibo.cn/{userId}?page={page}&filter=1"; - string text = await HttpHelper.GetAsync(url, dataSource, cookie); + string text = await HttpHelper.GetAsync(url, dataSource, cookie!); var doc = new HtmlDocument(); doc.LoadHtml(text); @@ -389,14 +383,14 @@ await Task.Run(async () => using (File.Create(downloadFolder + userId + "//" + nickName)) { } var desc = docUser.DocumentNode.Descendants("div").Where(x => x.Attributes["class"]?.Value == "tip2").ToList()?[0].InnerText.Split(" "); - string weiboDesc = string.Join(" ", desc); + string weiboDesc = string.Join(" ", desc!); headUrl = "https://tvax2.sinaimg.cn/large/" + Path.GetFileName(temp).Split("?")[0]; var fileName = downloadFolder + userId + "//" + Path.GetFileName(headUrl); //下载头像 if (!File.Exists(fileName)) { - await HttpHelper.GetAsync(headUrl, dataSource, cookie, fileName); + await HttpHelper.GetAsync(headUrl, dataSource, cookie!, fileName); } Image_Head?.Dispatcher.InvokeAsync(() => @@ -411,7 +405,7 @@ await Task.Run(async () => Image_Head.ImageSource = bi; //Image_Head.ImageSource = new BitmapImage(new Uri(fileName)); - TextBlock_UID.Text = userId; + TextBlock_UID!.Text = userId; TextBlock_NickName.Text = nickName; TextBlock_WeiboDesc.Text = weiboDesc; }); @@ -497,7 +491,7 @@ await Task.Run(async () => List originalPics = new List(); if (picType == PicEnum.Pictures) { - text = await HttpHelper.GetAsync(photoListUrl, dataSource, cookie); + text = await HttpHelper.GetAsync(photoListUrl, dataSource, cookie!); var doc2 = new HtmlDocument(); doc2.LoadHtml(text); list = doc2.DocumentNode.Descendants("img").ToList().ToList(); @@ -523,10 +517,10 @@ await Task.Run(async () => { try { - var res = await HttpHelper.GetAsync(videoUrl, dataSource, cookie); + var res = await HttpHelper.GetAsync(videoUrl, dataSource, cookie!); if (res != null && res.ok == 1) { - originalPics.Add(res?.data?.@object?.stream?.hd_url); + originalPics.Add(res?.data?.@object?.stream?.hd_url!); } } catch @@ -558,7 +552,7 @@ await Task.Run(async () => Debug.WriteLine(fileName); //已存在的文件超过设置值,判定该用户下载过了 - if (settings.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) + if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) { AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info); isSkip = true; @@ -576,7 +570,7 @@ await Task.Run(async () => //传入图片/视频的名字,开始下载图片/视频 try { - await HttpHelper.GetAsync(item, dataSource, cookie, fileName); + await HttpHelper.GetAsync(item, dataSource, cookie!, fileName); //修改文件日期时间为发博的时间 File.SetCreationTime(fileName, timestamp); @@ -606,10 +600,293 @@ await Task.Run(async () => await Task.Delay(rnd); } } + //通过m.weibo.cn移动端api获取 + else if (dataSource == WeiboDataSource.WeiboCnMobile) + { + //https://m.weibo.cn/api/container/getIndex?type=uid&value=10000000000&containerid=10760310000000000&since_id=5040866311798795 + long sinceId = 0; + bool cachedUserInfo = false; + string personalFolder = userId; + + while (true) + { + if (isSkip) + break; + + string url = $"https://m.weibo.cn/api/container/getIndex?type=uid&value={userId}&containerid=107603{userId}&since_id={sinceId}"; + var res = await HttpHelper.GetAsync(url, dataSource, cookie!); + if (res != null && res?.Ok == 1 && res?.Data != null && res?.Data?.Cards != null && res?.Data?.Cards?.Count > 0) + { + sinceId = (long)res?.Data?.CardlistInfo?.SinceId!; + AppendLog($"获取到{res?.Data?.CardlistInfo?.Total}条数据", MessageEnum.Info); + + + //获取用户资料 + if (!cachedUserInfo) + { + nickName = res?.Data?.Cards?[0]?.Mblog?.User?.ScreenName!; + personalFolder = $"{nickName}({userId})"; + Directory.CreateDirectory(downloadFolder + "//" + personalFolder); + using (File.Create(downloadFolder + "//" + personalFolder + "//" + nickName)) { } + + headUrl = "https://tvax2.sinaimg.cn/large/" + Path.GetFileName(res?.Data?.Cards?[0]?.Mblog?.User?.AvatarHd!).Split("?")[0]; + var fileName = downloadFolder + "//" + personalFolder + "//" + Path.GetFileName(headUrl); + //下载头像 + if (!File.Exists(fileName)) + { + await HttpHelper.GetAsync(headUrl, dataSource, cookie!, fileName); + } + + Image_Head?.Dispatcher.InvokeAsync(() => + { + var bytes = File.ReadAllBytes(fileName); + MemoryStream ms = new MemoryStream(bytes); + BitmapImage bi = new BitmapImage(); + bi.BeginInit(); + bi.StreamSource = ms; + bi.EndInit(); + + Image_Head.ImageSource = bi; + + //Image_Head.ImageSource = new BitmapImage(new Uri(fileName)); + TextBlock_UID!.Text = userId; + TextBlock_NickName.Text = nickName; + TextBlock_WeiboDesc.Text = res?.Data?.Cards?[0]?.Mblog?.User?.Description!; + }); + + cachedUserInfo = true; + } + + //获取图片列表中的每一个图片的原图超链接url + List originalPics = new List(); + List originalVideos = new List(); + List originalLivePhotos = new List(); + string weiboContent = ""; + DateTime timestamp = DateTime.Now; + + foreach (var card in res?.Data?.Cards!) + { + if (isSkip) + break; + //9是微博,RetweetedStatus是转发 + if (card?.CardType != 9 || card?.Mblog?.RetweetedStatus != null) + continue; + + if (cancellationTokenSource.IsCancellationRequested) + { + AppendLog("用户手动终止。", MessageEnum.Info); + return; + } + originalPics.Clear(); + originalVideos.Clear(); + originalLivePhotos.Clear(); + weiboContent = card?.Mblog?.Text!; + string format = "ddd MMM dd HH:mm:ss K yyyy"; // 定义日期格式 + + timestamp = DateTime.ParseExact(card?.Mblog?.CreatedAt!, format, System.Globalization.CultureInfo.InvariantCulture); + + if (card?.Mblog?.PicIds != null && (bool)card?.Mblog?.PicIds?.Any()!) + { + foreach (var item in card?.Mblog?.PicIds!) + { + var photoUrl = "https://wx4.sinaimg.cn/large/" + Path.GetFileName(item) + ".jpg"; + originalPics.Add(photoUrl); + } + } + if (card?.Mblog?.LivePhoto != null && (bool)(card?.Mblog?.LivePhoto?.Any()!)) + { + foreach (var item in card?.Mblog?.LivePhoto!) + { + originalLivePhotos.Add(item); + } + } + //选最高清晰度 + if (card?.Mblog?.PageInfo?.Urls?.Mp48kMp4 != null) + { + originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp48kMp4!); + } + else if (card?.Mblog?.PageInfo?.Urls?.Mp44kMp4 != null) + { + originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp44kMp4!); + } + else if (card?.Mblog?.PageInfo?.Urls?.Mp42kMp4 != null) + { + originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp42kMp4!); + } + else if (card?.Mblog?.PageInfo?.Urls?.Mp41080pMp4 != null) + { + originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp41080pMp4!); + } + else if (card?.Mblog?.PageInfo?.Urls?.Mp4720pMp4 != null) + { + originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp4720pMp4!); + } + else if (card?.Mblog?.PageInfo?.Urls?.Mp4HDMp4 != null) + { + originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp4HDMp4!); + } + else if (card?.Mblog?.PageInfo?.Urls?.Mp4LDMp4 != null) + { + originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp4LDMp4!); + } + + //替换非法字符 + var invalidChar = Path.GetInvalidFileNameChars(); + var newCaption = invalidChar.Aggregate(weiboContent, (o, r) => (o.Replace(r.ToString(), string.Empty))); + var fileName = downloadFolder + "//" + personalFolder + "//" + "//" + + timestamp.ToString("yyyy-MM-dd HH_mm_ss") + newCaption; + if (fileName.Length > 240) + fileName = HttpHelper.GetUniqueFileName(fileName.Substring(0, 240) + Path.GetExtension(fileName)); + Debug.WriteLine(fileName); + + //下载获取图片列表中的图片原图 + foreach (var item in originalPics) + { + if (isSkip) + break; + + if (string.IsNullOrEmpty(item)) + continue; + var fileNamee = fileName + ".jpg"; + fileNamee = HttpHelper.GetUniqueFileName(fileNamee.Substring(0, fileNamee.Length - 4) + Path.GetExtension(fileNamee)); + if (fileNamee.Length > 240) + fileNamee = HttpHelper.GetUniqueFileName(fileNamee.Substring(0, 240) + Path.GetExtension(fileNamee)); + //已存在的文件超过设置值,判定该用户下载过了 + if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) + { + AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info); + isSkip = true; + } + + //已经下载过的跳过 + if (File.Exists(fileNamee)) + { + AppendLog("文件已存在,跳过下载" + fileNamee, MessageEnum.Warning); + countDownloadedSkipToNextUser++; + await Task.Delay(500); + continue; + } + + //传入图片/视频的名字,开始下载图片/视频 + try + { + await HttpHelper.GetAsync(item, dataSource, cookie!, fileNamee); + + //修改文件日期时间为发博的时间 + File.SetCreationTime(fileNamee, timestamp); + File.SetLastWriteTime(fileNamee, timestamp); + File.SetLastAccessTime(fileNamee, timestamp); + + AppendLog("已完成 " + Path.GetFileName(fileNamee), MessageEnum.Success); + } + catch (Exception ex) + { + AppendLog($"文件下载失败,原始url:{item},下载路径{fileNamee}", MessageEnum.Error); + } + } + foreach (var item in originalVideos) + { + if (isSkip) + break; + + if (string.IsNullOrEmpty(item)) + continue; + var fileNamee = fileName + ".mp4"; + fileNamee = HttpHelper.GetUniqueFileName(fileNamee.Substring(0, fileNamee.Length - 4) + Path.GetExtension(fileNamee)); + if (fileNamee.Length > 240) + fileNamee = HttpHelper.GetUniqueFileName(fileNamee.Substring(0, 240) + Path.GetExtension(fileNamee)); + //已存在的文件超过设置值,判定该用户下载过了 + if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) + { + AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info); + isSkip = true; + } + + //已经下载过的跳过 + if (File.Exists(fileNamee)) + { + AppendLog("文件已存在,跳过下载" + fileNamee, MessageEnum.Warning); + countDownloadedSkipToNextUser++; + await Task.Delay(500); + continue; + } + + //传入图片/视频的名字,开始下载图片/视频 + try + { + await HttpHelper.GetAsync(item, dataSource, cookie!, fileNamee); + + //修改文件日期时间为发博的时间 + File.SetCreationTime(fileNamee, timestamp); + File.SetLastWriteTime(fileNamee, timestamp); + File.SetLastAccessTime(fileNamee, timestamp); + + AppendLog("已完成 " + Path.GetFileName(fileNamee), MessageEnum.Success); + } + catch (Exception ex) + { + AppendLog($"文件下载失败,原始url:{item},下载路径{fileNamee}", MessageEnum.Error); + } + } + foreach (var item in originalLivePhotos) + { + if (isSkip) + break; + + if (string.IsNullOrEmpty(item)) + continue; + var fileNamee = fileName + ".mov"; + fileNamee = HttpHelper.GetUniqueFileName(fileNamee.Substring(0, fileNamee.Length - 4) + Path.GetExtension(fileNamee)); + if (fileNamee.Length > 240) + fileNamee = HttpHelper.GetUniqueFileName(fileNamee.Substring(0, 240) + Path.GetExtension(fileNamee)); + //已存在的文件超过设置值,判定该用户下载过了 + if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser) + { + AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info); + isSkip = true; + } + + //已经下载过的跳过 + if (File.Exists(fileNamee)) + { + AppendLog("文件已存在,跳过下载" + fileNamee, MessageEnum.Warning); + countDownloadedSkipToNextUser++; + await Task.Delay(500); + continue; + } + + //传入图片/视频的名字,开始下载图片/视频 + try + { + await HttpHelper.GetAsync(item, dataSource, cookie!, fileNamee); + + //修改文件日期时间为发博的时间 + File.SetCreationTime(fileNamee, timestamp); + File.SetLastWriteTime(fileNamee, timestamp); + File.SetLastAccessTime(fileNamee, timestamp); + + AppendLog("已完成 " + Path.GetFileName(fileNamee), MessageEnum.Success); + } + catch (Exception ex) + { + AppendLog($"文件下载失败,原始url:{item},下载路径{fileNamee}", MessageEnum.Error); + } + } + } + + } + } + //通过加入随机等待避免被限制。爬虫速度过快容易被系统限制(一段时间后限制会自动解除),加入随机等待模拟人的操作,可降低被系统限制的风险。默认是每爬取1到5页随机等待6到10秒,如果仍然被限,可适当增加sleep时间 + Random rd = new Random(); + int rnd = rd.Next(5000, 10000); + AppendLog($"随机等待{rnd}ms,避免爬虫速度过快被系统限制", MessageEnum.Info); + await Task.Delay(rnd); + } + //单个用户结束下载 string info = $"{userId}{nickName}于{DateTime.Now.ToString("HH:mm:ss")}结束下载,程序版本V{currentVersion}"; - await PushPlusHelper.SendMessage(settings?.PushPlusToken, "微博相册下载", info); + await PushPlusHelper.SendMessage(settings?.PushPlusToken!, "微博相册下载", info); AppendLog(info, MessageEnum.Info); } } @@ -640,14 +917,9 @@ private void AppendLog(string text, MessageEnum messageEnum = MessageEnum.Info) }); } - //private void ToggleSwitch_Source_Click(object sender, RoutedEventArgs e) - //{ - // ToggleSwitch_Source.Content = ToggleSwitch_Source.Content.ToString() == "weibo.com" ? "weibo.cn" : "weibo.com"; - // isFromWeiboCom = ToggleSwitch_Source.Content.ToString() == "weibo.com"; - //} - private void InitData() { + //配置文件不存在就创建 if (!File.Exists("uidList.txt")) { using (File.Create("uidList.txt")) { } @@ -714,6 +986,17 @@ private void InitData() } } + #region UI操作 + private async void StartDownLoad(object sender, RoutedEventArgs e) + { + await Start(); + } + + private void StopDownLoad(object sender, RoutedEventArgs e) + { + cancellationTokenSource?.Cancel(); + } + private void OpenDir(object sender, RoutedEventArgs e) { Process.Start("explorer.exe", Path.GetFullPath(downloadFolder)); @@ -738,9 +1021,13 @@ private void ComboBox_DataSource_SelectionChanged(object sender, System.Windows. } else if (ComboBox_DataSource.SelectedIndex == 1) { - AppendLog("通过weibo.com获取的是相册信息,可以获取原创微博相册、头像相册、自拍相册等。少数用户存在获取失败的问题,怀疑是微博内部api不统一造成的。", MessageEnum.Info); + AppendLog("通过m.weibo.cn获取的是用户的时间流,推荐使用。", MessageEnum.Info); } else if (ComboBox_DataSource.SelectedIndex == 2) + { + AppendLog("通过weibo.com获取的是相册信息,可以获取原创微博相册、头像相册、自拍相册等。少数用户存在获取失败的问题,怀疑是微博内部api不统一造成的。", MessageEnum.Info); + } + else if (ComboBox_DataSource.SelectedIndex == 3) { AppendLog("通过weibo.com获取的是用户的ajax相册,可以获取原创微博相册、头像相册、自拍相册等。但是获取不到博文信息,所以无法重命名图片和修改图片日期。貌似还获取不全。不推荐使用!!!", MessageEnum.Info); } @@ -751,18 +1038,28 @@ private void MicaWindow_SizeChanged(object sender, SizeChangedEventArgs e) Border_Head.Width = Border_Head.Height = Column_LeftGrid.ActualWidth * 0.8; Border_Head.CornerRadius = new CornerRadius(Column_LeftGrid.ActualWidth * 0.8); } + #endregion + /// + /// 通过Selenium获取Cookie + /// + /// + /// private void GetCookie(object sender, RoutedEventArgs e) { string loginUrl = "https://passport.weibo.com/sso/signin?entry=wapsso&source=wapssowb&url=https://weibo.cn"; - if (ComboBox_DataSource.SelectedIndex != 0) + if (ComboBox_DataSource.SelectedIndex == 1) + { + loginUrl = "https://passport.weibo.com/sso/signin?entry=wapsso&source=wapsso&url=https://m.weibo.cn"; + } + else if (ComboBox_DataSource.SelectedIndex == 2 || ComboBox_DataSource.SelectedIndex == 3) { loginUrl = "https://passport.weibo.com/sso/signin?entry=miniblog&source=miniblog&url=https://weibo.com/"; } IWebDriver driver = new ChromeDriver(); driver.Url = loginUrl; - WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(60)) + WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromHours(8)) { PollingInterval = TimeSpan.FromMilliseconds(500), }; @@ -770,11 +1067,11 @@ private void GetCookie(object sender, RoutedEventArgs e) //wait.Until(d => d.FindElement(By.LinkText("title"))); // 等待页面加载完成并获取页面标题 - wait.Until(d => d.Title.Equals("微博 – 随时随地发现新鲜事") || d.Title.Equals("我的首页")); + wait.Until(d => d.Title.Equals("微博 – 随时随地发现新鲜事") || d.Title.Equals("我的首页") || d.Title.Equals("微博")); // 获取页面标题并进行检查 string pageTitle = driver.Title; - if (pageTitle.Equals("微博 – 随时随地发现新鲜事") || pageTitle.Equals("我的首页")) + if (pageTitle.Equals("微博 – 随时随地发现新鲜事") || pageTitle.Equals("我的首页") || pageTitle.Equals("微博")) { AppendLog("扫码登陆成功", MessageEnum.Success); // 获取所有的 Cookie 对象 diff --git a/WeiboAlbumDownloader/Models/AlbumDetailModel2.cs b/WeiboAlbumDownloader/Models/AlbumDetailModel2.cs index bf5f40f..573434a 100644 --- a/WeiboAlbumDownloader/Models/AlbumDetailModel2.cs +++ b/WeiboAlbumDownloader/Models/AlbumDetailModel2.cs @@ -1,16 +1,12 @@ using Newtonsoft.Json; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace WeiboAlbumDownloader.Models { public partial class AlbumDetailModel2 { [JsonProperty("data")] - public PhotoListData2 PhotoListData2 { get; set; } + public PhotoListData2? PhotoListData2 { get; set; } [JsonProperty("ok")] public long Ok { get; set; } @@ -19,10 +15,10 @@ public partial class AlbumDetailModel2 public partial class PhotoListData2 { [JsonProperty("type")] - public string Type { get; set; } + public string? Type { get; set; } [JsonProperty("list")] - public List PhotoListItem2 { get; set; } + public List? PhotoListItem2 { get; set; } [JsonProperty("since_id")] public long SinceId { get; set; } @@ -31,24 +27,24 @@ public partial class PhotoListData2 public partial class PhotoListItem2 { [JsonProperty("pid")] - public string Pid { get; set; } + public string? Pid { get; set; } [JsonProperty("mid")] - public string Mid { get; set; } + public string? Mid { get; set; } [JsonProperty("is_paid")] public bool IsPaid { get; set; } [JsonProperty("timeline_month")] - public string TimelineMonth { get; set; } + public string? TimelineMonth { get; set; } [JsonProperty("timeline_year")] - public string TimelineYear { get; set; } + public string? TimelineYear { get; set; } [JsonProperty("object_id")] - public string ObjectId { get; set; } + public string? ObjectId { get; set; } [JsonProperty("type")] - public string Type { get; set; } + public string? Type { get; set; } } } diff --git a/WeiboAlbumDownloader/Models/SettingsModel.cs b/WeiboAlbumDownloader/Models/SettingsModel.cs index 7634cd3..ee14ba1 100644 --- a/WeiboAlbumDownloader/Models/SettingsModel.cs +++ b/WeiboAlbumDownloader/Models/SettingsModel.cs @@ -3,15 +3,15 @@ public class SettingsModel { //weibo.cn cookie - public string WeiboCnCookie { get; set; } + public string? WeiboCnCookie { get; set; } //weibo.com cookie - public string WeiboComCookie { get; set; } + public string? WeiboComCookie { get; set; } //推送到微信,填了就会发送 - public string PushPlusToken { get; set; } + public string? PushPlusToken { get; set; } //是否开启Crontab定时任务 public bool EnableCrontab { get; set; } = true; //Crontab定时任务 - public string Crontab { get; set; } = "14 2 * * *"; + public string? Crontab { get; set; } = "14 2 * * *"; //用来跳过到下一个uid的计数。如果当前uid下载的时候已存在文件超过此计数,则判定下载过了。-1表示不判定 public int CountDownloadedSkipToNextUser { get; set; } = 20; } diff --git a/WeiboAlbumDownloader/Models/WeiboCnMobileModel.cs b/WeiboAlbumDownloader/Models/WeiboCnMobileModel.cs new file mode 100644 index 0000000..32ff7b3 --- /dev/null +++ b/WeiboAlbumDownloader/Models/WeiboCnMobileModel.cs @@ -0,0 +1,500 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Security.Policy; + +namespace WeiboAlbumDownloader.Models +{ + public partial class WeiboCnMobileModel + { + [JsonProperty("ok")] + public long Ok { get; set; } + + [JsonProperty("data")] + public Data? Data { get; set; } + } + + public partial class Data + { + [JsonProperty("cardlistInfo")] + public CardlistInfo? CardlistInfo { get; set; } + + [JsonProperty("cards")] + public List? Cards { get; set; } + + [JsonProperty("scheme")] + public string? Scheme { get; set; } + + [JsonProperty("showAppTips")] + public long ShowAppTips { get; set; } + } + + public partial class CardlistInfo + { + [JsonProperty("containerid")] + public string? Containerid { get; set; } + + [JsonProperty("v_p")] + public long VP { get; set; } + + [JsonProperty("show_style")] + public long ShowStyle { get; set; } + + [JsonProperty("total")] + public long Total { get; set; } + + [JsonProperty("autoLoadMoreIndex")] + public long AutoLoadMoreIndex { get; set; } + + [JsonProperty("since_id")] + public long SinceId { get; set; } + } + + public partial class Card + { + [JsonProperty("card_type")] + public long CardType { get; set; } + + [JsonProperty("profile_type_id")] + public string? ProfileTypeId { get; set; } + + [JsonProperty("itemid")] + public string? Itemid { get; set; } + + [JsonProperty("scheme")] + public Uri? Scheme { get; set; } + + [JsonProperty("mblog")] + public Mblog? Mblog { get; set; } + } + + public partial class Mblog + { + [JsonProperty("visible")] + public Visible? Visible { get; set; } + + [JsonProperty("created_at")] + public string? CreatedAt { get; set; } + + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("mid")] + public string? Mid { get; set; } + + [JsonProperty("can_edit")] + public bool CanEdit { get; set; } + + [JsonProperty("text")] + public string? Text { get; set; } + + [JsonProperty("textLength")] + public long TextLength { get; set; } + + [JsonProperty("source")] + public string? Source { get; set; } + + [JsonProperty("favorited")] + public bool Favorited { get; set; } + + [JsonProperty("pic_ids")] + public List? PicIds { get; set; } + + [JsonProperty("thumbnail_pic")] + public string? ThumbnailPic { get; set; } + + [JsonProperty("bmiddle_pic")] + public string? BmiddlePic { get; set; } + + [JsonProperty("original_pic")] + public string? OriginalPic { get; set; } + + [JsonProperty("is_paid")] + public bool IsPaid { get; set; } + + [JsonProperty("mblog_vip_type")] + public long MblogVipType { get; set; } + + [JsonProperty("user")] + public User? User { get; set; } + + [JsonProperty("retweeted_status")] + public RetweetedStatus? RetweetedStatus { get; set; } + + [JsonProperty("reposts_count")] + public long RepostsCount { get; set; } + + [JsonProperty("comments_count")] + public long CommentsCount { get; set; } + + [JsonProperty("reprint_cmt_count")] + public long ReprintCmtCount { get; set; } + + [JsonProperty("attitudes_count")] + public long AttitudesCount { get; set; } + + [JsonProperty("mixed_count")] + public long MixedCount { get; set; } + + [JsonProperty("pending_approval_count")] + public long PendingApprovalCount { get; set; } + + [JsonProperty("isLongText")] + public bool IsLongText { get; set; } + + [JsonProperty("show_mlevel")] + public long ShowMlevel { get; set; } + + [JsonProperty("darwin_tags")] + public List? DarwinTags { get; set; } + + [JsonProperty("ad_marked")] + public bool AdMarked { get; set; } + + [JsonProperty("mblogtype")] + public long Mblogtype { get; set; } + + [JsonProperty("item_category")] + public string? ItemCategory { get; set; } + + [JsonProperty("rid")] + public string? Rid { get; set; } + + [JsonProperty("extern_safe")] + public long ExternSafe { get; set; } + + [JsonProperty("number_display_strategy")] + public NumberDisplayStrategy? NumberDisplayStrategy { get; set; } + + [JsonProperty("content_auth")] + public long ContentAuth { get; set; } + + [JsonProperty("is_show_mixed")] + public bool IsShowMixed { get; set; } + + [JsonProperty("comment_manage_info")] + public CommentManageInfo? CommentManageInfo { get; set; } + + [JsonProperty("pic_num")] + public long PicNum { get; set; } + + [JsonProperty("mlevel")] + public long Mlevel { get; set; } + + [JsonProperty("region_name")] + public string? RegionName { get; set; } + + [JsonProperty("region_opt")] + public long RegionOpt { get; set; } + + [JsonProperty("analysis_extra")] + public string? AnalysisExtra { get; set; } + + [JsonProperty("mblog_menu_new_style")] + public long MblogMenuNewStyle { get; set; } + + [JsonProperty("edit_config")] + public EditConfig? EditConfig { get; set; } + + [JsonProperty("page_info")] + public PageInfo? PageInfo { get; set; } + + [JsonProperty("pics")] + public List? Pics { get; set; } + + [JsonProperty("live_photo")] + public List? LivePhoto { get; set; } + + [JsonProperty("bid")] + public string? Bid { get; set; } + + [JsonProperty("safe_tags")] + public long? SafeTags { get; set; } + } + + public partial class RetweetedStatus + { + } + + public partial class CommentManageInfo + { + [JsonProperty("comment_permission_type")] + public long CommentPermissionType { get; set; } + + [JsonProperty("approval_comment_type")] + public long ApprovalCommentType { get; set; } + + [JsonProperty("comment_sort_type")] + public long CommentSortType { get; set; } + } + + public partial class DarwinTag + { + [JsonProperty("object_type")] + public string? ObjectType { get; set; } + + [JsonProperty("object_id")] + public string? ObjectId { get; set; } + + [JsonProperty("display_name")] + public string? DisplayName { get; set; } + + [JsonProperty("enterprise_uid")] + public string? EnterpriseUid { get; set; } + + [JsonProperty("bd_object_type")] + public string? BdObjectType { get; set; } + } + + public partial class EditConfig + { + [JsonProperty("edited")] + public bool Edited { get; set; } + } + + public partial class NumberDisplayStrategy + { + [JsonProperty("apply_scenario_flag")] + public long ApplyScenarioFlag { get; set; } + + [JsonProperty("display_text_min_number")] + public long DisplayTextMinNumber { get; set; } + + [JsonProperty("display_text")] + public string? DisplayText { get; set; } + } + + public partial class PageInfo + { + [JsonProperty("type")] + public string? Type { get; set; } + + [JsonProperty("icon")] + public string? Icon { get; set; } + + [JsonProperty("page_pic")] + public PagePic? PagePic { get; set; } + + [JsonProperty("page_url")] + public string? PageUrl { get; set; } + + [JsonProperty("page_title")] + public string? PageTitle { get; set; } + + [JsonProperty("content1")] + public string? Content1 { get; set; } + + [JsonProperty("content2")] + public string? Content2 { get; set; } + + [JsonProperty("video_orientation")] + public string? VideoOrientation { get; set; } + + [JsonProperty("play_count")] + public string? PlayCount { get; set; } + + [JsonProperty("media_info")] + public Mediainfo? Mediainfo { get; set; } + + [JsonProperty("urls")] + public Urls? Urls { get; set; } + } + + public partial class Mediainfo + { + [JsonProperty("stream_url")] + public string? StreamUrl { get; set; } + + [JsonProperty("stream_url_hd")] + public string? StreamUrlHd { get; set; } + + [JsonProperty("duration")] + public string? Duration { get; set; } + } + + public partial class Urls + { + [JsonProperty("mp4_8k_mp4")] + public string? Mp48kMp4 { get; set; } + + [JsonProperty("mp4_4k_mp4")] + public string? Mp44kMp4 { get; set; } + + [JsonProperty("mp4_2k_mp4")] + public string? Mp42kMp4 { get; set; } + + [JsonProperty("mp4_1080p_mp4")] + public string? Mp41080pMp4 { get; set; } + + [JsonProperty("mp4_720p_mp4")] + public string? Mp4720pMp4 { get; set; } + + [JsonProperty("mp4_hd_mp4")] + public string? Mp4HDMp4 { get; set; } + + [JsonProperty("mp4_ld_mp4")] + public string? Mp4LDMp4 { get; set; } + } + + public partial class PagePic + { + [JsonProperty("url")] + public string? Url { get; set; } + + [JsonProperty("width")] + public int Width { get; set; } + + [JsonProperty("height")] + public int Height { get; set; } + } + + public partial class Pic + { + [JsonProperty("pid")] + public string? Pid { get; set; } + + [JsonProperty("url")] + public string? Url { get; set; } + + [JsonProperty("size")] + public string? Size { get; set; } + + [JsonProperty("geo")] + public PicGeo? Geo { get; set; } + + [JsonProperty("large")] + public Large? Large { get; set; } + + [JsonProperty("videoSrc")] + public string? VideoSrc { get; set; } + + [JsonProperty("type")] + public string? Type { get; set; } + } + + public partial class PicGeo + { + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("croped")] + public bool Croped { get; set; } + } + + public partial class Large + { + [JsonProperty("size")] + public string? Size { get; set; } + + [JsonProperty("url")] + public string? Url { get; set; } + + [JsonProperty("geo")] + public LargeGeo? Geo { get; set; } + } + + public partial class LargeGeo + { + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("croped")] + public bool Croped { get; set; } + } + + public partial class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("screen_name")] + public string? ScreenName { get; set; } + + [JsonProperty("profile_image_url")] + public string? ProfileImageUrl { get; set; } + + [JsonProperty("profile_url")] + public string? ProfileUrl { get; set; } + + [JsonProperty("close_blue_v")] + public bool CloseBlueV { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + [JsonProperty("follow_me")] + public bool FollowMe { get; set; } + + [JsonProperty("following")] + public bool Following { get; set; } + + [JsonProperty("follow_count")] + public long FollowCount { get; set; } + + [JsonProperty("followers_count")] + public string? FollowersCount { get; set; } + + [JsonProperty("cover_image_phone")] + public string? CoverImagePhone { get; set; } + + [JsonProperty("avatar_hd")] + public string? AvatarHd { get; set; } + + [JsonProperty("badge")] + public Dictionary? Badge { get; set; } + + [JsonProperty("statuses_count")] + public long StatusesCount { get; set; } + + [JsonProperty("verified")] + public bool Verified { get; set; } + + [JsonProperty("verified_type")] + public long VerifiedType { get; set; } + + [JsonProperty("gender")] + public string? Gender { get; set; } + + [JsonProperty("mbtype")] + public long Mbtype { get; set; } + + [JsonProperty("svip")] + public long Svip { get; set; } + + [JsonProperty("urank")] + public long Urank { get; set; } + + [JsonProperty("mbrank")] + public long Mbrank { get; set; } + + [JsonProperty("followers_count_str")] + public string? FollowersCountStr { get; set; } + + [JsonProperty("verified_reason")] + public string? VerifiedReason { get; set; } + + [JsonProperty("like")] + public bool Like { get; set; } + + [JsonProperty("like_me")] + public bool LikeMe { get; set; } + + [JsonProperty("special_follow")] + public bool SpecialFollow { get; set; } + } + + public partial class Visible + { + [JsonProperty("type")] + public long Type { get; set; } + + [JsonProperty("list_id")] + public long ListId { get; set; } + } +}