Welcome to our website.

How I Once Batch-Restored Previously Downloaded Songs from Xiami

This method no longer works and only applied to an older version of Xiami.

Before getting into it

This is not a guide to grabbing Xiami’s high-quality tracks for free in bulk. It came out of a very specific problem: I was too lazy to manually recover a large library, so I pieced together a workaround instead. The code uses PHP, cURL, and JavaScript, and it makes the most sense to run it locally.

One more thing matters more than anything else: this is not about bypassing Xiami’s payment system. You still had to have enough Xiami credits or red-envelope balance to download the songs. Without that, none of this would help.

Why I ended up doing this

A couple of days earlier I had installed Linux Mint. I opened my music folder there, and before long the system froze. I didn’t pay much attention at the time. But once I went back to Windows, I got a warning that the folder was corrupted, and the files inside were effectively gone with it.

I tried recovery software, but the results were disappointing. That library was around 400 songs and more than 2 GB in total—hardly something I wanted to give up on. That was the reason for everything that follows.

The basic idea

Xiami’s download flow used to work roughly like this:

  1. When you clicked download on the website, the site sent the song information into your account’s undownloaded list.
  2. That undownloaded list then returned a file beginning with the custom emoun:// protocol, which launched the Xiami desktop downloader. This worked in the same general spirit as thunder:// or ed2k:// links.
  3. After opening, the client checked the undownloaded list, retrieved the songs, and downloaded them.
  4. At the same time, those songs were moved from the undownloaded list into the downloaded list.

That completed a normal download cycle.

download process

As shown above, clicking the download button submitted the relevant data to http://www.xiami.com/download/song, and the page returned a file using the emoun:// protocol.

So the workaround was straightforward in theory:

  • log in and scrape the songs already present in the downloaded list,
  • submit those songs back into the undownloaded list in bulk,
  • then open the Xiami client and let it query the list.

If that assumption held, the client should pull every queued track at once, after which all that remained was waiting for the downloads to finish.

Step 1: get the login cookies

The first step was to save the cookies from a logged-in session so the later scraping requests would work. Edit lines 2 and 3 to enter your own account and password. If the login succeeded, a cookies.txt file would be created in the same directory.

$name = ''; //输入你的账号
$password = ''; //输入你的密码
$curl_post = 'email='.$name.'&password='.$password.'&done=/&submit=登 录';
$cookie_file = dirname(__FILE__).'/cookie.txt';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "http://www.xiami.com/member/login");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $curl_post);
curl_setopt($curl, CURLOPT_COOKIEJAR, $cookie_file);
curl_exec($curl);
curl_close($curl);

Step 2: scrape the downloaded list and prepare it for resubmission

Once the cookies were available, the next part was fairly simple. The main issue was efficiency.

In my case there were roughly 400 songs, and Xiami displayed 20 songs per page, which meant around 20 pages to fetch. That was already a noticeable workload for PHP, especially on a local machine. If your library was small, the code below was enough. If you had a larger number of pages, it would be better to rewrite it using multi_curl, convert it to an AJAX-style approach, or split the work into batches.

When using this code, remember to set the number of pages on line 3.

set_time_limit(0); //设置成不限制页面运行时间
$page = ''; //填写你的已经下载页面的页数
$cookie_file = dirname(__FILE__).'/cookie.txt';

$song = '';
//如果页数过多可以分批获取,以免等待时间过长
for($i=1;$i<=$page;$i++) {
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, "http://www.xiami.com/account/mysongs/s/2/page/".$i);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
    $data = curl_exec($curl);
    curl_close($curl);

    $preg = '/\<td.class\=\"iname\"\>\<a.href\=\"\/song\/(.*?)\"\>(.*?)\<\/a\>/s';
    preg_match_all($preg, $data, $match);
    foreach($match[1] as $item) {
        $song .= $item . ',';
    }
}
$song = str_replace(' ', '', $song);
$song = substr($song, 0, -1);
echo "var song = new Array($song);";
/*end*/

The script outputs the song IDs as a JavaScript array. That was done deliberately, because the next stage involved sending one POST request per song. Even 400 requests could feel heavy in plain PHP on a local setup, so it made more sense to let JavaScript handle the looping and fire the requests asynchronously.

Step 3: submit the songs back in bulk

The idea here was simple:

  • JavaScript loops through the song IDs.
  • PHP receives the ID and performs the POST request to Xiami.

When using the code, replace line 3 in xiami.song.download.html with the output generated in the previous step.

//将第二步获得的代码复制到下面,记得删除最后一位数的逗号
var song = new Array(1769629171,3338870,1769902385,1770432131,2868586,1770149760,367633,1769832130,35526,1769962750,1770551145,1770457081,3486103,1769400110,127903,3367334,2078855,71830,376007,376015,2074918,2730,1770450144,3455520,79157,79151,81366,65256,390070,2138604,1770345727,376050,376006,149193,388168,1769665592,3497062,1769236069,3632104,1768923954,1769491757,3608275,2092882,1770354410,3480460,3620143,2095376,2561774,3664677,1770462404,1769056924,3527076,2126340,2089410,1769177482,2083322,1769831952,1769316186,1770524409,1769517803,1190507,1964547,1769107542,3486203,1769686818,1769028326,1858814,2084011,1769235816,1770614545,2083187,2605276,378268,2515019,1122167,1769176497,3302053,2083102,3638520,189072,371017,1770168732,1083760,2122948,2070331,2128868,3319126,2561592,1381654,3441719,1768939471,2091290,2098665,2067235,1770060224,3562953,54342,1769831786,1770145312,76323,2067242,173117,136054,1770068043,1769833104,1769833105,1769833106,1769833107,1769833108,2082305,1768989931,1769356638,1769082273,1769776879,1769740304,1205851,1768984809,1769102373,1769740095,1768989928,1769381931,1769227674,3599015,1768962602,3463177,1769381938,1769381935,1769381934,1769381932,3410377,2067242,3381901,3381903,373969,2072395,373971,373990,374039,3381910,3381911,3381912,382134,378711,193010,162269,143775,127673,120041,89443,52732,382512,383210,1769274527,1769004670,1768958960,3599311,3365855,2314604,2080987,2073790,385907,115384,374057,377936,380029,382560,382852,383962,33806,378041,380287,385729,385760,388406,389153,381833,83126,386773,389072,389080,389077,43542,52726,375183,382821,384595,385074,386954,373818,376387,378307,379302,382777,384646,384670,385137,385976,386073,386347,2342433,3550893,1769699970,2095102,2095107,2017034,2286524,2342421,2286523,3338183,1768988423,2286527,2380720,3464931,1176114,1769334977,2155384,1769839800,3225258,2091936,1769334987,3187867,2095104,3413844,1769072635,1007695,3636957,3467770,1768940269,1769292436,1769850021,1769102619,3409064,1769870173,1769850022,1769850019,1769463117,3446206,2028877,2136483,1769072646,1769291745,1769072643,1769113798,196142,3187959,2079575,2286522,2079581,2385273,1769673320,3513664,1769071426,3502285,1769801604,1769801605,3484274,1769303054,1769303056,1769303057,1769303058,1769303059,1769303060,1769303061,1769303062,1769303055,1768914983,378646,3586293,1769830111,1769830112,1769830113,1769830114,1769830115,1769830116,1769830117,1769830118,1769830119,1769830120,1769570445,);

//复制结束

//下面的代码不需要修改
var XHR; //定义一个全局对象
function xm_download(id){
    if(window.ActiveXObject){//IE的低版本系类
        XHR=new ActiveXObject('Microsoft.XMLHTTP');
    }else if(window.XMLHttpRequest){//非IE系列的浏览器,但包括IE7 IE8
        XHR=new XMLHttpRequest();
    }
    XHR.open("GET","download.php?id="+id,true);
    XHR.send(null);
}
for (var i=0;i<song.length;i++) {
    xm_download(song[i]);
}
$data = 'pid=&ptype=&song_count=1&id='.$_GET['id'];
$cookie_file = dirname(__FILE__).'/cookie.txt'; //登录COOKIES文件地址
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "http://www.xiami.com/download/song");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$data = curl_exec($curl);
curl_close($curl);

Step 4: open the Xiami client and check the queue

The last step was done in the desktop app itself. Open Xiami and choose File → Check unfinished downloads. After a short wait, the queued songs should appear. From there, it was just a matter of letting the client handle the batch download.

That was the whole workaround: scrape the songs already recorded as downloaded, push them back into the pending list, and let the official client do the rest.

Related Posts