利用Google快照,恢复误删的WordPress数据库
起因:前些天试图修复MySQL经常性崩溃的问题,结果误删WordPress数据库(误删ibdata文件后重启了MySQL进程,失去了恢复机会)。由于之前从未做过备份,本博客所有文章全部丢失。但Google默默地为我的网站作了快照,所以仍然有机会从Google快照中恢复网站内容。
抓取快照网址
1. 将Google搜索每页搜索结果数设为最大值100
2. 使用Google搜索site:jinzihao.me
3. 对于每页搜索结果(“for page in search_results:”),在浏览器控制台中执行以下代码:
function saveData(data, filename) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}
var regex = /"(https:\/\/webcache.googleusercontent.com\/.*?)"/g;
var urls = [];
match = regex.exec(document.body.innerHTML);
while (match != null) {
urls.push(match[1] + '&vwsrc=1');
match = regex.exec(document.body.innerHTML);
}
saveData(new Blob([urls.join("\n")]), 'url.txt');
上述代码提取网页中所有快照链接(https://webcache.googleusercontent.com/……),将其保存到文件url.txt。添加”vwsrc=1″参数使Google快照显示网页原始的HTML代码,便于之后重建网站内容。
由于搜索结果页数不多,本人采用了手动翻页的方式来抓取快照链接。之后将几个网址列表(url.txt)合并到一个文件,这就是下一步抓取的目标列表了。
图1. 抓取到的快照网址列表
抓取快照内容
1. 打开一个Google快照页面,确保其在https://webcache.googleusercontent.com域名下,这样我们才能通过AJAX请求抓取其他快照页面。
2. 将前一步抓取的快照网址列表放置在服务器上,并在服务器上设置好CORS策略,这样我们可以通过AJAX请求获取到该列表。
3. 在浏览器控制台中执行以下代码:
function saveData(data, filename) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}
xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "https://lab.jinzihao.me/cors/urllist.txt", false);
xmlhttp.send();
urls = xmlhttp.responseText.split("\n");
for (var url of urls) {
console.log(url);
xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", url, false);
xmlhttp.send();
url = url.split("+")[0].split(":")[4];
console.log(url);
saveData(new Blob([xmlhttp.responseText]), "jinzihao_me_" + btoa(url));
}
这样就将网站在Google快照中的所有页面抓取了下来,其中文件名是jinzihao_me_ + base64编码的原始网址,文件内容则是Google快照页面的全部HTML代码。
图2. 抓取到的快照页面
重建网站
首先清空wp_posts表,确保文章ID不会产生冲突:
truncate table wp_posts;
接下来使用如下Python程序,遍历上一步抓取到的快照页面,并将其插回到MySQL数据库,重建wp_posts表(由于MySQLdb模块只支持Python 2,本代码只能运行在Python 2上):
#!/usr/bin/env python
#!-*- coding: UTF-8 -*-
import os
import sys
import html
import HTMLParser
from bs4 import BeautifulSoup
import time
import MySQLdb
os.environ['TZ'] = 'Asia/Chongqing'
time.tzset()
result = dict()
db = MySQLdb.connect('localhost', '****username****', '****password****', '****dbname****', charset='utf8')
cursor = db.cursor()
for filename in os.listdir(sys.argv[1]):
print("File: " + filename)
# f = open(os.path.join(sys.argv[1], filename), 'r', encoding='utf-8')
f = open(os.path.join(sys.argv[1], filename), 'r')
content = f.read()
f.close()
soup = BeautifulSoup(content, 'html.parser')
# inner_content = html.unescape(soup.find_all('pre')[0].decode_contents())
inner_content = HTMLParser.HTMLParser().unescape(soup.find_all('pre')[0].decode_contents())
inner_soup = BeautifulSoup(inner_content, 'html.parser')
permalinks = []
gmt_times = []
local_times = []
post_types = []
metas = inner_soup.select('span.meta')
for meta in metas:
if len(meta.a['href'].split('#')) == 1:
if meta.abbr is None:
post_types.append('page')
post_time = '1970-01-01 08:00:00'
else:
post_types.append('post')
post_time = meta.abbr['title']
gmt_times.append(post_time[:10] + ' ' + post_time[11:19])
local_times.append(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(time.mktime(time.strptime(gmt_times[-1], '%Y-%m-%d %H:%M:%S')) + 57600)))
permalinks.append(meta.a['href'])
posts = inner_soup.select('div.postcontent')
for id, post in enumerate(posts):
post_id = int(post['id'].split('-')[1])
if post.h2 is None:
post_title = ''
post_content = post.decode_contents()
elif post.h2.a is None:
post_title = post.h2.decode_contents()
post_content = post.decode_contents()
post_content = post_content[post_content.find('</h2>') + 5:]
else:
post_title = post.h2.a.decode_contents()
post_content = post.decode_contents()
post_content = post_content[post_content.find('</h2>') + 5:]
if (post_id not in result) or (len(post_content) > len(result[post_id][1])):
result[post_id] = [post_title, post_content, gmt_times[id], local_times[id], permalinks[id], permalinks[id].split('/')[-2], post_types[id]]
for id, data in result.items():
sql = "INSERT INTO wp_posts (ID, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);"
cursor.execute(sql, (id, 1, data[3], data[2], data[1], data[0], '', 'publish', 'open', 'open', '', data[5], '', '', data[3], data[2], '', 0, data[4], 0, data[6], '', 0))
db.commit()
db.close()
这段代码使用Beautiful Soup对Google快照页面做了解析,提取最外层的<pre>元素,其中包含被快照的网页的HTML代码。对其进行一次HTML反转义,再次用Beautiful Soup进行解析,提取其中的<span class=”meta”>,获取到文章的元信息(时间和网址);提取其中的<div class=”postcontent”>,获取到文章ID和内容。最后将其插入到wp_posts表,即可重建WordPress文章数据。
高飞 上午12:46 on 2018年8月15日 链接地址 |
非常棒的技术,有编程方面的问题向你请教,看到麻烦回复/联系,谢谢!997868589@qq.com
jinzihao 上午11:29 on 2018年8月15日 链接地址 |
您好,我的邮箱是903703287@qq.com,欢迎邮件联系
陈伟 下午10:12 on 2018年9月1日 链接地址 |
20世纪下载站怎么乱七八糟了
jinzihao 上午9:02 on 2018年9月3日 链接地址 |
系统有一些已知的bug,不知是否被人恶意利用了
陈文 下午10:07 on 2018年10月5日 链接地址
20世纪下载站打不开了
陈文 下午10:19 on 2018年9月1日 链接地址 |
还有20世纪下载站的后台管理网址是多少