当前位置

自己编程进行Drupal 6网站升级数据迁移到Drupal 7

James Qi 在 2015年12月24日 - 15:32 提交
内容摘要:整个2015年从开始到结尾都在进行网站向云服务器的搬迁以及网站的升级,其中大数据量的数据迁移是个令人很头痛的问题,几百万的数据量加上几十个字段,系列网站还有几十个这样的网站,需要等待数据迁移程序运行的......

  整个2015年从开始到结尾都在进行网站向云服务器的搬迁以及网站的升级,其中大数据量的数据迁移是个令人很头痛的问题,几百万的数据量加上几十个字段,系列网站还有几十个这样的网站,需要等待数据迁移程序运行的时间真是太长太长了。上半年就遇到大数据量的问题,后来通过修改服务器配置,让PHP使用更多的内存、最大执行时间、数据库连接缓存等办法,还是用drush content-migrate-fields这样的命令来进行,算是解决了部分难以迁移的站点。但现在到年尾,而且随着Drupal 8的退出,Drupal 6很快就面临失去支持的境况,我们需要把所有Drupal 6网站都升级,现在把所有服务器资源都利用起来,还专门购买16核64G内存的云服务器临时实例来加快迁移程序的运行,但按目前的做法算下来,依然是不知道还需要几个月才能完成。sad

  最早写博文《大数据量Drupal_6网站升级到Drupal_7很麻烦》的时候,采用了数据库命令来升级的办法,这无疑是最快的,但因为其它系列站数据结构复杂、数据导入及删除等情况也复杂,所以没有敢这样做。再后来一想,drush命令其实很好,但却N个字段逐个字段进行迁移,这样用的时间就是N多倍了,也尝试过想修改drush命令中涉及到的程序来加快进度,但那个程序还比较复杂,看了半天也没有看懂,不知道如何下手修改。正好前几个在搬迁一个数据量不大的英文站时,需要编写一个PHP程序来进行一些数据的匹配、修改、保存,就想到干脆自己来编写程序进行Drupal 6到Drupal 7的数据迁移,经过几天的编写、调试和使用,证明这是可行的,本来准备一个程序直接读取Drupal 6站点数据、马上写入Drupal 7站点,但调用API初始化上有些问题,就用了两个PHP程序,中间使用csv文件来做过渡:

  1. drupal6-file.php :按内容类型读取Drupal 6网站中的node数据,保存到csv文件中
  2. file-drupal7.php :逐行读取csv文件,保存到Drupal 7网站对应的内容类型中(这一步其实也可以用feeds模块来进行导入)

  drupal6-file.php示范程序源代码如下:

<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
$_SERVER['HTTP_HOST'] = 'chn.youbianku.com';
$_SERVER['REQUEST_URI'] = '/';
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$_SERVER['SCRIPT_NAME'] = '/drupal6-file.php';
$drupal6_path = '/alidata/www/chn.youbianku.com/';
chdir($drupal6_path);
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

if (isset($argv[1])) {
    $file_name = $argv[1];
} else {
    $file_name = "/root/my_array.csv";
}

if (isset($argv[2])) {
    $offset = $argv[2];
} else {
    $offset = 0;
}

if (isset($argv[3])) {
    $limit = $argv[3];
} else {
    $limit = 10;
}

$node_type = "address";
$type = $node_type;

print "file_name = $file_name offset = $offset limit = $limit\n";

$fields = content_types($type);
//print "fields['fields'] = ".$fields['fields']." \n";
$count=0;
//print_r($fields);

$fields_array=array();
foreach ( $fields['fields'] as $key => $field ) {
    $fields_array[]=$key;
}
//print_r($fields_array);

$file = fopen($file_name,"w");

$head_array = $fields_array;
array_unshift($head_array,'title');
array_unshift($head_array,'nid');
fputcsv($file,$head_array);
print "$count,".implode(",",$head_array)."\n";

//$sql = "SELECT node.title, node.type, node.nid FROM {node} WHERE node.type = '$node_type' LIMIT $list_no OFFSET $offset";
$sql = "SELECT node.nid FROM {node} WHERE node.type = '$node_type' LIMIT $limit OFFSET $offset";
$result = db_query($sql);
while ($anode = db_fetch_object($result)) {
    //print_r ($anode);
    $nid = $anode->nid;
    $node=node_load($nid);
    $node_array = get_object_vars($node);
    //print_r ($node_array);
    $line_array=array();
    foreach ($fields_array as $field) {
        $line_array[$field] = $node_array[$field][0]['value'];
    }
    array_unshift($line_array,$node->title);
    array_unshift($line_array,$nid);
    $count++;
    print "$count,".implode(",",$line_array)."\n";
    fputcsv($file,$line_array);
    unset($nid,$node,$node_array,$line_array,$field);
}

fclose($file);
print "total count = $count\n";
print "-- end --\n";
?>

  file-drupal7.php示范程序源代码如下:

<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
$_SERVER['HTTP_HOST'] = 'chn.youbianku.com';
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$_SERVER['SCRIPT_NAME'] = '/file-drupal7.php';
$drupal7_path = '/alidata/www/chn.youbianku.com/';
chdir($drupal7_path);
define('DRUPAL_ROOT', $drupal7_path);
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

if (isset($argv[1])) {
    $file_name = $argv[1];
} else {
    print "please provide file_name";
    exit;
}

$file = fopen($file_name,"r");
$count=0;
//$line = split(',',$fields_array);
//print "line = $line\n";
//array_unshift($fields_array,'title');
//array_unshift($fields_array,'nid');
$fields_array=fgetcsv($file);
print "$count,".implode(",",$fields_array)."\n";
array_shift($fields_array);
array_shift($fields_array);

while(! feof($file)) {
    $line_array = fgetcsv($file);
    if ($line_array == FALSE) break;
    $count++;
    print "$count,".implode(",",$line_array)."\n";
    $nid = array_shift($line_array);
    $title = array_shift($line_array);

    $node=node_load($nid);
//  print_r($node);
    $node_array = get_object_vars($node);
    //print_r($node_array);
    foreach($line_array as $key => $field_value) {
      $field_name = $fields_array[$key];
      $node_array[$field_name]['und'][0]['value'] = $field_value;
    }
    $node_new = (object) $node_array;
    node_save($node_new);
    unset($nid,$title,$node,$node_array,$line_array,$key,$field_value,$field_name,$node_new);
}

fclose($file);
print "total count = $count\n";
print "-- end --\n";
?>

  对于超大数据量的网站来说,运行以上drupal6-file.php程序也需要很长时间,而且占用内存非常大(几十万的数量可能超过10G),可以用一个shell批处理调用php程序,分段来处理。而file-drupal7.php占用内存很少,不过依然需要很长时间运行,主要是等待RDS数据库服务器的IO。


  补充:以上程序经过好些天的运行,证明是可以迁移数百万数量级页面的网站,不过对于一些drupal 6中内容为空的字段,用drush content-migrate-fields迁移是不在drupal 7中的字段产生新的纪录,而用上面的程序还是会产生内容为空格('')的记录,显得不必要和占用空间,可以用下面类似的语句来判断和删除:

DELETE FROM `postcodebase_tx`.`field_data_field_urbcitystate` WHERE `field_urbcitystate_value` = '';
DELETE FROM `postcodebase_tx`.`field_data_field_urbcitystate` WHERE `field_urbcitystate_value` is null;

  2016年2月23日补充:上面是从drupal 6读取字段保存到csv文件,然后再读取csv文件保存到drupal 7的字段,中间用了一个csv文件作为中转。这几天还在做一些其它网站的数据迁移收尾工作,遇到分类字段,就尝试了不用csv文件过渡,而是在drupal 7中直接读取数据库中drupal 6老版本数据表中的字段内容,保存到对应的drupal 7的字段中,这样更简单、快捷,程序例子:

<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
$_SERVER['HTTP_HOST'] = 'trade.mingluji.com';
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$_SERVER['SCRIPT_NAME'] = '/trade.php';
$drupal7_path = '/alidata/www/drupal_update.mingluji.com/';
chdir($drupal7_path);
define('DRUPAL_ROOT', $drupal7_path);
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

$count=0;
$count_change=0;
$count_not_change=0;
$count_null=0;

if (isset($argv[1])) {
    $node_type = $argv[1];
} else {
    print "please provide node_type";
    exit;
}
if (isset($argv[2])) {
    $offset = $argv[2];
} else {
    $offset = 0;
}
if (isset($argv[3])) {
    $limit = $argv[3];
} else {
    $limit = 100000;
}

//$node_type = "gongying";

print "node_type = $node_type, offset = $offset, limit = $limit\n";

$sql = "SELECT node.nid FROM {node} WHERE node.type = '$node_type' LIMIT $limit OFFSET $offset";
$result = db_query($sql);

while ($anode = $result->fetch()) {
$count++;
$node=node_load($anode->nid);
//$node=node_load('51254');
$nid=$node->nid;
if ($nid == NULL) {
    print "nid is null\n";
    $count_null++;
    continue;
}

$sql1 = "SELECT field_area_value FROM content_field_area WHERE nid=$nid";
$result1 = db_query($sql1);
$array1 = $result1->fetch();
//print_r($array1);
$tid = $array1->field_area_value;

$new=$node;
if ($tid != NULL && $tid != $new->field_area['und'][0]['tid']) {
//print_r($new);
$new->field_area['und'][0]['tid']=$tid;
node_save($new);
$count_change++;
} else {
$count_not_change++;
}

print "count=$count, nid=$nid, tid=$tid\n";
}

echo "Done!\n";
print "count=$count, count_change=$count_change, count_not_change=$count_not_change, count_null = $count_null\n";
?>

  上面程序已经在需要的网站中运行成功,如果需要修改字段名称或者是多值字段的话,可以另存为多个程序来执行。