珂珂的个人博客 - 一个程序猿的个人网站

QQ空间相册批量下载

    网上找的基本都是需要钱的,不然下载不完全等限制,这岂能难倒我们程序员呢,于是自己动手写了一个。

在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("获取授权成功");
            }
        }
    }
}




上一篇:多线程域名查询工具

下一篇:个人代码全部开源


0 评论

查看所有评论

给个评论吧