网上找的基本都是需要钱的,不然下载不完全等限制,这岂能难倒我们程序员呢,于是自己动手写了一个。
在2013 年已经写了一个基础的并且已实现下载,当时没有找到如何授权,部分采用IE拦截的方式,但这种拦截方式只支持到IE8还是IE9。 过了2年多不知QQ空间改版没有,这次终于知道如何获取cookie和g_tk了,于是拿最初的版本重新优化了一下并加入多线程下载。
关于授权,必须有当前的cookie和g_tk 网上查到这个g_tk算法,我把java改成.net
先获取登陆后的cookie,一定要登陆后的。
string cook = CookieHelper.GetCookies("http://user.qzone.qq.com/" + this.txtGrantQQ.Text.Trim());
这个cookie里面有个skey 取出来再用下面的方法计算
public String GetG_TK(String str) { int hash = 5381; for (int i = 0, len = str.Length; i < len; ++i) { hash += (hash << 5) + (int)str.ToCharArray()[i]; } return (hash & 0x7fffffff) + ""; }
有这2个东西就可以向腾讯的服务器请求相册列表json和图片列表json了。
多线程调度真是个麻烦的事,这里纠结了2个小时
完整代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Threading.Tasks; using QzoneDown.UI.Util; using System.Threading; using System.Collections.ObjectModel; using System.Text.RegularExpressions; using System.Data; using System.IO; using FYJ.Data.Min; namespace QzoneDown.UI { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { private string cookie; private string g_tk; private string uin; private string domain_s; private QzoneHelper qzoneHelper = new QzoneHelper(); private CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); public MainWindow() { InitializeComponent(); } protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); qzoneHelper.MessageEvent += QzoneHelper_MessageEvent; this.btnSuspend.IsEnabled = false; this.btnStop.IsEnabled = false; } void QzoneHelper_MessageEvent(object sender, QzoneEventArgs e) { if (e.Code > 0) { if (chkShowLog.IsChecked == true) { this.logList1.AddItem(e.Message); } } else if (e.Code == 0) { this.logList1.AddWarnItem(e.Message); } else { this.logList1.AddErrorItem(e.Message); } } #region 从网络获取相册 private async void btnGetAlbum_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(this.txtQQ.Text.Trim())) { MessageBox.Show("请输入QQ号"); return; } this.toolStripStatusLabel1.Text = "正在获取相册列表..."; string qq = this.txtQQ.Text.Trim(); List<Album> list = new List<Album>(); await Task.Run(() => { try { list = this.qzoneHelper.GetAlbum(qq, this.cookie, this.uin, this.g_tk, out this.domain_s); //保存相册到数据库 DataBase.SaveAlbumList(list); } catch (Exception ex) { this.logList1.AddErrorItem("获取相册列表错误:" + ex.Message); } }); this.comboBox1.SelectedValuePath = "ID"; this.comboBox1.DisplayMemberPath = "Name"; this.comboBox1.ItemsSource = list; this.comboBox1.Tag = list; if (list.Count > 0) { this.comboBox1.SelectedIndex = 0; } this.toolStripStatusLabel1.Text = "就绪"; } #endregion #region 从数据库加载相册 private void btnLoadAlbumFromDB_Click(object sender, RoutedEventArgs e) { if (Regex.IsMatch(this.txtQQ.Text.Trim(), "^\\d+$")) { DataTable dt = DbHelper.Instance.GetDataTable("select * from qzone_tb_album where QQ='" + this.txtQQ.Text.Trim() + "'"); List<Album> list = FYJ.Data.Min.EntityHelper<Album>.DataTableToModel(dt); this.comboBox1.SelectedValuePath = "ID"; this.comboBox1.DisplayMemberPath = "Name"; this.comboBox1.ItemsSource = list; this.comboBox1.Tag = dt; if (dt.Rows.Count > 0) { this.comboBox1.SelectedIndex = 0; } MessageBox.Show("从数据库加载相册列表成功"); } else { MessageBox.Show("请输入正确的QQ号"); } } #endregion #region 从网络加载单个相册 private async void btnLoadSingleFromNet_Click(object sender, RoutedEventArgs e) { Album album = this.comboBox1.SelectedItem as Album; if (album.allowAccess == 0) { MessageBox.Show("无权查看该相册"); return; } this.toolStripStatusLabel1.Text = "正在获取图片列表..."; List<Photo> list = new List<Photo>(); await Task.Run(() => { try { string json = null; list = this.qzoneHelper.GetPic(album.QQ, album.ID, album.Name, ref this.domain_s, this.cookie, this.uin, this.g_tk, out json); if (!String.IsNullOrEmpty(json)) { album.PhotoJson = json; DataBase.SavePhotoJson(album); } DataBase.SavePhotoList(list); } catch (Exception ex) { this.logList1.AddErrorItem("获取图片列表错误:" + ex.Message); } }); this.gridAlbum.ItemsSource = list; this.toolStripStatusLabel1.Text = "就绪"; } #endregion #region 从网络加载所有相册 private async void btnLoadAllFromNet_Click(object sender, RoutedEventArgs e) { this.toolStripStatusLabel1.Text = "正在获取图片列表..."; List<Photo> totalPhotoList = new List<Photo>(); List<Album> list = this.comboBox1.ItemsSource as List<Album>; this.progressBarEx1.Maximum = list.Count; this.progressBarEx1.Value = 0; Task t = new Task(() => { for (int i = 0; i < list.Count; i++) { Album album = list[i]; try { this.progressBarEx1.Value = i + 1; if (album.allowAccess == 0) { this.logList1.AddErrorItem("无权查看相册:" + album.Name); continue; } this.toolStripStatusLabel1.Text = "正在获取相册[" + album.Name + "]..."; string json = null; List<Photo> plist = this.qzoneHelper.GetPic(album.QQ, album.ID, album.Name, ref this.domain_s, this.cookie, this.uin, this.g_tk, out json); if (!String.IsNullOrEmpty(json)) { album.PhotoJson = json; DataBase.SavePhotoJson(album); } DataBase.SavePhotoList(plist); foreach (Photo p in plist) { totalPhotoList.Add(p); } } catch (Exception ex) { this.logList1.AddErrorItem("获取相册[" + album.Name + "]列表错误:" + ex.Message); } } }); t.Start(); await t; this.gridAlbum.ItemsSource = totalPhotoList; this.toolStripStatusLabel1.Text = "就绪"; } #endregion #region 从数据库加载单个相册 private void btnLoadSingleFromDB_Click(object sender, RoutedEventArgs e) { DbHelper db = DbHelper.Instance; DataTable dt = db.GetDataTable("select * from qzone_tb_pic where AlbumID=@AlbumID" , db.CreateParameter("@AlbumID", this.comboBox1.SelectedValue.ToString())); List<Photo> list = FYJ.Data.Min.EntityHelper<Photo>.DataTableToModel(dt); this.gridAlbum.ItemsSource = list; } #endregion #region 从数据库中加载所有相册 private void btnLoadAllFromDB_Click(object sender, RoutedEventArgs e) { DbHelper db = DbHelper.Instance; DataTable dt = db.GetDataTable("select * from qzone_tb_pic where QQ=@QQ" , db.CreateParameter("@QQ", this.txtQQ.Text.Trim())); List<Photo> list = FYJ.Data.Min.EntityHelper<Photo>.DataTableToModel(dt); this.gridAlbum.ItemsSource = list; } #endregion #region 下载列表 private async void btnDown_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(this.txtSavePath.Text.Trim())) { MessageBox.Show("请选择保存位置"); return; } this.toolStripStatusLabel1.Text = "正在下载列表中的图片..."; this.logList1.Items.Clear(); this.btnSuspend.IsEnabled = true; this.btnStop.IsEnabled = true; this.cancelTokenSource = new CancellationTokenSource(); (this.cancelTokenSource.Token.WaitHandle as ManualResetEvent).Set(); List<Photo> list = this.gridAlbum.ItemsSource as List<Photo>; //分配线程任务 int threadCount = Convert.ToInt32(txtThreadCount.Text.Trim()); if(threadCount>list.Count) { threadCount = list.Count; } this.progressBarEx1.Value = 0; this.progressBarEx1.Maximum = list.Count; List<Photo>[] clist = new List<Photo>[threadCount]; int index = 0; for(int i=0;i<list.Count;i++) { if(clist[index]==null) { clist[index] = new List<Photo>(); } clist[index].Add(list[i]); index++; if(index>=threadCount) { index = 0; } } //任务列表 List<Task> taskList = new List<Task>(); for(int k=0;k<threadCount;k++) { //下面两句一定不能放在 Task里面 List<Photo> pp = clist[k]; int threadindex = k; Task downTask = Task.Factory.StartNew(() => { try { String localFolder = System.IO.Path.Combine(this.txtSavePath.Text.Trim(), this.txtQQ.Text.Trim()); foreach (Photo p in pp) { try { if (cancelTokenSource.IsCancellationRequested) { this.progressBarEx1.Value = 0; this.btnSuspend.IsEnabled = false; this.btnStop.IsEnabled = false; return; } CancellationToken token = this.cancelTokenSource.Token; token.WaitHandle.WaitOne(); this.progressBarEx1.Value = this.progressBarEx1.Value + 1; string image = p.lasturl; if (!String.IsNullOrEmpty(p.raw2)) { image = p.raw2; } //加上随机数避免重复 string fileName = p.PicName + "_" + p.Ran; string filePath = System.IO.Path.Combine(localFolder, fileName); string albumName = p.AlbumName; if (!String.IsNullOrEmpty(albumName)) { filePath = System.IO.Path.Combine(localFolder, albumName, fileName); } //从物理文件判断是否已经下载过 if (File.Exists(fileName)) { this.logList1.AddWarnItem(fileName + "已存在"); continue; } if (p.IsDownload) { this.logList1.AddWarnItem("图片ID:" + p.ID + " 已经下载"); continue; } this.toolStripStatusLabel1.Text = ("任务"+ threadindex + "正在从" + image + "下载图片..." + this.progressBarEx1.Value + "/" + list.Count); if (chkShowLog.IsChecked == true) { this.logList1.AddItem("任务" + threadindex + "正在从" + image + "下载图片..."); } HttpHelper.DownloadImage(image, filePath, false, this.cookie); //更新下载状态 DataBase.UpdatePhotoDownloaded(p.ID); } catch (Exception ex) { this.logList1.AddErrorItem(ex.Message); } } } catch (Exception ex) { this.logList1.AddErrorItem("下载列表中的图片失败:" + ex.Message); } }); taskList.Add(downTask); } foreach (Task task in taskList) { await task; } //Task.WaitAll(taskList.ToArray()); this.btnSuspend.IsEnabled = false; this.btnStop.IsEnabled = false; this.logList1.AddItem("完成"); this.toolStripStatusLabel1.Text = "就绪"; } #endregion private void btnBrowser_Click(object sender, RoutedEventArgs e) { System.Windows.Forms.FolderBrowserDialog dig = new System.Windows.Forms.FolderBrowserDialog(); if (dig.ShowDialog() == System.Windows.Forms.DialogResult.OK) { this.txtSavePath.Text = dig.SelectedPath; } } private void 复制URLToolStripMenuItem_Click(object sender, RoutedEventArgs e) { Photo p = this.gridAlbum.SelectedItem as Photo; Clipboard.SetText(p.lasturl); MessageBox.Show(p.lasturl + "已复制到剪贴板"); } private void btnClear_Click(object sender, RoutedEventArgs e) { this.gridAlbum.ItemsSource = null; } private void btnSuspend_Click(object sender, RoutedEventArgs e) { if (this.btnSuspend.Content.ToString() == "暂停") { (this.cancelTokenSource.Token.WaitHandle as ManualResetEvent).Reset(); this.btnSuspend.Content = "继续"; } if (this.btnSuspend.Content.ToString() == "继续") { (this.cancelTokenSource.Token.WaitHandle as ManualResetEvent).Set(); this.btnSuspend.Content = "暂停"; } } private void btnStop_Click(object sender, RoutedEventArgs e) { cancelTokenSource.Cancel(); } public String GetG_TK(String str) { int hash = 5381; for (int i = 0, len = str.Length; i < len; ++i) { hash += (hash << 5) + (int)str.ToCharArray()[i]; } return (hash & 0x7fffffff) + ""; } #region 加载网页 private void btnLoad_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(this.txtGrantQQ.Text.Trim())) { MessageBox.Show("请输入授权的QQ号"); return; } if (Regex.IsMatch(this.txtGrantQQ.Text.Trim(), "^\\d+$")) { this.logList1.Items.Clear(); this.frmWeb.Navigate(new Uri("http://user.qzone.qq.com/" + this.txtGrantQQ.Text.Trim())); } else { MessageBox.Show("请输入正确的授权QQ号"); } } #endregion private void btnGetGrant_Click(object sender, RoutedEventArgs e) { //WebBrowser wb = (WebBrowser)frmWeb.Content; if (String.IsNullOrEmpty(this.txtGrantQQ.Text.Trim())) { MessageBox.Show("请输入授权的QQ号"); return; } this.uin = this.txtGrantQQ.Text.Trim(); string cook = CookieHelper.GetCookies("http://user.qzone.qq.com/" + this.txtGrantQQ.Text.Trim()); string skey = ""; if (cook != null) { this.cookie = cook; Match m = Regex.Match(cook, "skey=(@.*?);"); if (m.Success) { skey = m.Groups[1].Value.Trim(); } } if (!String.IsNullOrEmpty(skey)) { this.g_tk = GetG_TK(skey); } if (String.IsNullOrEmpty(cook) || String.IsNullOrEmpty(g_tk)) { MessageBox.Show("获取授权失败"); } else { MessageBox.Show("获取授权成功"); } } } }