EasyTranslator 开发过程总结(二)
前言
上篇文章介绍的开发环境的准备,这篇文章将介绍各个翻译接口爬虫的实现方案。
所谓的爬虫就是用程序模拟浏览器访问网络服务器,获取数据并解析数据的过程。在爬虫的过程中有几点注意的地方:
- 优先爬取移动版网页。原因是移动版文档结构比较简单,其次是有的网站的 PC 版有反爬虫策略,而移动版没有,例如后面将要讲到的 Google 翻译。
- 优先获取 json 数据。有的网站提供了返回 json 数据的接口,如果发现网站有这样的接口,要优先向该接口发送请求,而不是直接获取 html 文档。
- 解析文档提取数据时,首先要分析文档的结构,找出待提取数据周围的关键“特征”(标签、属性、内容),进而缩小范围,再利用文档节点的层次关系(父、子,兄弟等)进行定位。
1 定向爬虫的一般步骤
定向爬虫的一般步骤为:
2 爬取 Google 翻译
首先爬取 Google 翻译,下面就按照上面的步骤来。
使用 Google chrome 打开 Google 翻译的网页 (https://translate.google.cn/),下面我们就使用 chrome 的开发者工具看看,当我们在文本框中输入文本点击翻译按钮后,浏览器向服务器发送了那些请求,而服务器又作了那些响应。
在 chrome 中打开开发者工具 -> Network -> All,先 clear 一下,然后 F5 刷新以下,就可以看到如下界面。
可以看到,浏览器与服务器之间的交互信息,我们所关心的是 xhr 类型的信息。
点击某一条项目,左侧显示请求和响应的内容。利用 requests 爬虫时,就是根据这些请求的信息(URL、request headers、query string parameters 等)模拟浏览器发送 get 或 post 请求,从服务器获取数据。
点击 Response 的确看到服务器返回了 json 格式的翻译结果。
可是仔细看看 Headers 中的query string parameters ,发现有一个奇怪的 tk 值,这个 tk 值的计算较为复杂,由 js 计算出来的,是 google 翻译的反爬虫手段。tk 值不正确当然就无法从服务器得到翻译结果。于是,不得不寻找其它方案。
是否存在其它移动版或简版的 google 翻译网页呢?还真的有,我也是偶然间发现的(https://translate.google.cn/m)。不过这个接口不提供 json 数据格式,只提供 html 文档。
首先,应该利用 chrome 开发者工具的分析 html 文档,找出待提取数据的特征。
通过分析发现,翻译的结果隐藏在 一个div 标签中,并且带有属性 dir 和 class。那么不就可以利用 BeautifulSoup 的 find 方法进行定位了吗。完整代码如下:
def googleTraslator(text, flg=0):
url = 'https://translate.google.cn/m'
headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Mobile Safari/537.36'}
params = {
'hl': 'zh-CN',
'sl': 'zh-CN',
'tl': 'en',
'ie': 'UTF-8',
'q': text
}
if flg != 0:
params['sl'] = 'en'
params['tl'] = 'zh-CN'
rs = requests.get(url, headers=headers, params=params, timeout=1)
if rs.status_code != 200:
print('请求错误代码: ', rs.status_code)
return None
soup = BeautifulSoup(rs.text.replace('\r\n', ''), 'lxml')
return soup.find('div', {'dir': 'ltr', 'class': 't0'}).text
3 爬取百度翻译
爬取百度翻译的过程和上面爬取 Google 翻译的过程差不过,百度翻译 PC 版也有反爬虫策略,于是采用移动版的URL,模拟浏览器发送 post 请求,确实成功了,可是没几天就挂掉了!可能是百度翻译服务器升级了反爬虫策略,连移动版的也不可以了。
办法总是有的,百度为开发者提供了翻译开放平台,必须注册为开发者,并创建应用,获取 APP ID 和密钥才能调用该接口。接口的调用方法访问技术文档.
下面给出实现代码:
def baiduTranslator(text, flg=0):
url = 'http://api.fanyi.baidu.com/api/trans/vip/translate'
headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Mobile Safari/537.36'}
appid = xxxxx
secretKey = 'xxxxxx'
salt = random.randint(32768, 65536)
t = str(appid) + text + str(salt) + secretKey
sign = md5(t.encode('utf-8')).hexdigest()
data = {
'from': 'zh',
'to': 'en',
'q': text,
'appid': 20180906000203440,
'salt': salt,
'sign': sign
}
if flg != 0:
data['from'] = 'en'
data['to'] = 'zh'
rs = requests.post(url, headers=headers, data=data, timeout=1)
if rs.status_code != 200:
print('请求错误代码: ', rs.status_code)
return None
return rs.json()['trans_result'][0]['dst']
4 爬取金山词霸、必应翻译
这两个翻译网站比较“厚道”,没有严格的反爬虫策略,并且可以直接获取 json 数据,爬虫比较方便。
必应翻译:
def bingTranslator(text, flg=0):
url = 'https://cn.bing.com/ttranslate'
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36'}
data = {
'text': text,
'from': 'zh-CHS',
'to': 'en'
}
if flg != 0:
data['from'] = 'en'
data['to'] = 'zh-CHS'
rs = requests.post(url, headers=headers, data=data, timeout=1)
if rs.status_code != 200:
print('请求错误代码: ', rs.status_code)
return None
return rs.json().get('translationResponse')
金山词霸翻译:
def jinshanTranslator(text, flg=0):
url = 'http://fy.iciba.com/ajax.php'
headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Mobile Safari/537.36'}
params = {'a': 'fy'}
data = {'f': 'zh', 't': 'en', 'w': text}
if flg != 0:
data['f'] = 'en'
data['t'] = 'zh'
if text[-1] != '.':
data['w'] = text + '.'
rs = requests.post(url, params=params, headers=headers, data=data, timeout=1)
if rs.status_code != 200:
print('请求错误代码: ', rs.status_code)
return None
return rs.json().get('content').get('out')
5 爬取有道翻译
有道翻译的爬取方法和 Google 翻译类似,也是基于 html 文档解析。既可以采用 BeautifulSoup 也可以直接采用正则表达式提取翻译文本。下面介绍正则表达式的方法。
首先还是要分析 html 文档,利用 chrome 查看源代码,定位并提取翻译结果所在的文档节点。
<ul id="translateResult"><li>摘要建立了一套基于模块化算法的道路交通流量测量系统,用于道路车辆的交通流量分析。整个道路交通流量测量系统由三个部分组成:图像预处理模块、车辆检测模块和流量统计模块。</li></ul>
则对应的正则表达是的 pattern 为:
patern = re.compile(r'<ul id="translateResult">.*?<li>(.*?)</li>.*?</ul>', re.S)
re.S 参数为的是让 * 也能匹配空白字符(换行符等)。
完整代码如下:
def youdaoTranslator(text, flg=0):
url = 'http://m.youdao.com/translate'
headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Mobile Safari/537.36'}
data = {
'inputtext': text,
'type': 'ZH_CN2EN'
}
if flg != 0:
data['type'] = 'EN2ZH_CN'
rs = requests.post(url, headers=headers, data=data, timeout=1)
if rs.status_code != 200:
print('请求错误代码: ', rs.status_code)
return None
patern = re.compile(r'<ul id="translateResult">.*?<li>(.*?)</li>.*?</ul>', re.S)
m = re.search(patern, rs.text)
return m[1]
6 爬取 CNKI 翻译助手
CNKI 翻译助手和以上的翻译有所不同,它主要用于翻译专业词汇,并不是用来翻译长文本的。如果非要翻译长文本可能得不到结果。
CNKI 的爬虫还是基于 html 文档解析。不过,要比前面的较为复杂,因为所要提取的数据在文档中比较分散,要仔细分析 html 的特点,在定位关键信息的时候基于运用了 两个主要的技巧:
- 化大为小,缩小范围
- 先抓明显特征,再纵向或横向导航。
def cnkiTranslator(text, flg=0):
url = 'http://dict.cnki.net/dict_result.aspx'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36'}
params = {'searchword': text}
rs = requests.get(url, params=params, headers=headers, timeout=3)
if rs.status_code != 200:
print('请求错误代码: ', rs.status_code)
return None
# # print(rs.text)
# e = etree.HTML(rs.text)
# tables = e.xpath('//*[@id="lblresult"]/table[1]/tr/td[1]/table')
# parseTable(tables)
html = rs.text.replace('\r\n', '')
soup = BeautifulSoup(html, 'lxml')
tables = soup.select('table.main-table')
# print(len(e))
# print(e[0].text)
if len(tables) == 0:
print('没有查找到翻译结果!')
return None
result = []
for table in tables:
if table.img is not None and table.img.attrs['src'] == 'images/02.gif':
d1 = dict()
for t in table.select('font.text9'):
l = []
next_tr = t.parent.parent.next_sibling
tts = next_tr.findAll('font', {'class': 'text6'})
for tt in tts:
l.append(tt.text)
d1[t.text] = l[:min(4, len(l))]
# print(d1)
result.append(d1)
elif table.img is not None and table.img.attrs['src'] == 'images/word.jpg':
t2s = table.findAll('table', {'id': re.compile(r'showjd_\d')})
d2 = dict()
for t2 in t2s:
l = []
text_zhs = t2.select('td.text11Green')
for text_zh in text_zhs:
l.append(text_zh.parent.previous_sibling.text.strip())
l.append(text_zh.text.strip())
key = t2.previous_sibling.select('a[href^="javascript:showjdsw"]')[0].text
d2[key] = l[:min(4, len(l))]
# print(d2)
result.append(d2)
if len(result) == 2:
break
return result
爬取过程主要使用了 find, findAll 和 select 函数。其中 find 返回第一个匹配的对象。findAll 和 select 都返回 list
find 与 findAll 的参数格式一样,例如
r = obj.find('tag_name', {'property_name1': value1, 'property_name2': value2, ...})
而且属性值可以是正则表达式。
而 select 是基于 CSS 选择器语法的,不支持正则表达式。CSS 语法参见 W3school CSS 选择器参考手册.
7 总结
以上介绍了不同翻译网站的爬虫实现,思路都是相同的,只是从服务器得到的数据形式不同,解析数据的方法也有所不同。
在爬虫的过程中学会使用 Google chrome 的开发者工具分析浏览器与服务器之间的来往信息,分析 html文档的结构等。使用的教程可以参考 chrome 调试工具常用功能整理.