网上找的基本都是需要钱的,不然下载不完全等限制,这岂能难倒我们程序员呢,于是自己动手写了一个。
在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("获取授权成功");
}
}
}
}
珂珂的个人博客 - 一个程序猿的个人网站