在线教育网站Twinkl最近宣布,任何人都可以获得为期一个月的会员资格(注册网址:www.twinkl.com/offer ,注册时输入代码: USATWINKLHELPS)。Twinkl上的教育资源主要面向学前、小学教育,资源格式一般为PPT和PDF,制作精美,学前班和小学教师应该会非常喜欢。最近写了个批量爬取脚本,放上来交流一下。
一、思路分析
暂时没有发现 Twinkl上的资源是否存在一个全局的索引规则,所以我们可以首先选定某一个分类目录,从而批量下载该目录下的所有资源。如下图,每一个分类目录下有若干页面。每个页面有12个资源,除了第一页的链接需要手动获取外,后面第N页的链接格式为:第一页链接/N。
查看页面源代码,发现每一个资源都对应一个li标签,例如:
1
2
3
4
5
6
7
8
|
<li class="resource-preview col-12 col-sm-6 col-lg-4 col-xl-3 ">
<a href="/resource/ma-t-l-141-line-handwriting-activity-sheets">
<img src="https://images.twinkl.co.uk/tw1n/image/private/t_345/image_repo/0c/81/ma-t-l-141-line-handwriting-activity-sheets-mandarin-chinese_ver_1.jpg" data-width="480" data-src="https://images.twinkl.co.uk/tw1n/image/private/t_345/image_repo/0c/81/ma-t-l-141-line-handwriting-activity-sheets-mandarin-chinese_ver_1.jpg" class="delayed-image-load image-replace" title="" style="">
<noscript><img src="https://images.twinkl.co.uk/tw1n/image/private/t_345/image_repo/0c/81/ma-t-l-141-line-handwriting-activity-sheets-mandarin-chinese_ver_1.jpg" alt="画线运笔练习 - 运笔练习,手写,肌肉能力训练,直线,曲线,圆圈,波浪线,之字形折线," width="345" height="173"/></noscript>
<h2>画线运笔练习</h2><h3>More languages</h3></a><a class="saveIcon moreLanguages blue button" data-id="2796856" href="/my-saved-resources">保存以后用</a>
</li>
|
其中入口链接为<a>标签的href属性,缩略图链接为<img>标签的src属性或data-src属性,资源标题为<h2>标签内的文本。
进入任意资源页面,很容易可以看到下载入口链接下面的id为download_link的<a>标签内:
1
2
3
4
|
<span class="download flagPresentSpan">
<a id="download_link" data-download="no" data-nopremiumfollow="1" href="/sign-up" class="button bigDownload green flagPresent loggedOut">Unlimited 会员<div>下载</div></a>
</span>
|
访问此链接,可以通过post请求获得真实下载地址(这一步会校验登陆状态和会员资格)。下面图中location这一项即为真实下载地址,可以利用脚本获取。
此外,这里还可以顺便获得用户cookie,以便模拟登陆:
到这里,基本就可以理顺思路了。
二、功能实现
1.导入库
这里要用到如下库:
1 |
from bs4 import BeautifulSoup import re import requests import time
|
2.获取所有目录页面
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def getUrl(initialUrl, headers):
pageUrlList = []
r = requests.get(initialUrl, headers = headers)
soup = BeautifulSoup(r.text, from_encoding = 'utf-8')
div = soup.find('div', class_='pager_bar')
pageNum = int(div['data-num-pages'])
print 'Total page number:' + str(pageNum)
pageUrlList.append(initialUrl)
if pageNum >= 2:
for i in range(2,pageNum+1):
newPageUrl = initialUrl + '/' + str(pageNum)
pageUrlList.append(newPageUrl)
return pageUrlList, pageNum
|
此函数输入值为手动设置的初始链接(初始链接一般设为某一目录下的第一页),输出值为所有页面的序号和对应链接。
3.获取资源列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def getItem(pageUrl, headers):
itemUrlList = []
rr = requests.get(pageUrl, headers = headers)
rsoup = BeautifulSoup(rr.text, from_encoding = 'utf-8')
for link in rsoup.find_all('a', href = re.compile(r'/resource/.*')):
title = link.find('h2').text
itemUrltemp = link['href']
itemUrl = 'https://www.twinkl.com' + itemUrltemp
picUrlTemp = link.find('img')
try:
picUrl = picUrlTemp['src']
except:
picUrl = picUrlTemp['data-src']
temp = [title, itemUrl, picUrl]
itemUrlList.append(temp)
return itemUrlList
|
输入值为上一步获得的页面链接,批量获取每一个页面中每一个资源对应的入口链接和缩略图链接,最后汇总为一个列表。实际测试时总是在获取缩略图时报错,检查发现缩略图属性名称不统一,所以用了一个异常处理语句。
4.获取真实下载链接
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def getDownload(itemUrl):
rrr = requests.get(itemUrl, headers = headers)
rrsoup = BeautifulSoup(rrr.text, from_encoding = 'utf-8')
downTemp = rrsoup.find('a', id = 'download_link')['href']
downloadUrl = 'https://www.twinkl.com.hk' + downTemp
print '下载入口获取成功'
return downloadUrl
def convertUrl(downloadUrl):
downloadInfo = requests.post(downloadUrl, headers=headers, allow_redirects=False)
trueUrl = downloadInfo.headers['location']
print '下载链接获取成功'
return trueUrl
|
这两个函数分别用来获取下载入口链接和对应的真实地址。
5.下载资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def download(trueDownloadUrl, title, picUrl):
suffix = 'exe'
suffixList = ['.doc', '.ppt', '.zip', '.rar', '.pdf']
for l in suffixList:
if l in trueDownloadUrl:
suffix = l
fileName = title + str(suffix)
picName = title + '.jpg'
down = requests.get(trueDownloadUrl)
with open(fileName,"wb") as f:
f.write(down.content)
f.close()
picDown = requests.get(picUrl)
with open(picName, "wb") as p:
p.write(picDown.content)
p.close()
print '下载完成'
return None
|
由于在下载链接中看不到文件格式,所以预设了几个可能的后缀名,通过判断语句来确定。
6.调取函数运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
def managerItem(itemUrlList):
for j in range(0, len(itemUrlList)):
print 'j=' + str(j)
itemUrl = itemUrlList[j][1]
picUrl = itemUrlList[j][2]
title = itemUrlList[j][0].replace('/','').replace('*','')
print title
try:
downloadUrl = getDownload(itemUrl)
trueDownloadUrl = convertUrl(downloadUrl)
download(trueDownloadUrl, title, picUrl)
except Exception as e:
print '报错'
print e
time.sleep(60)
print '等待 60秒'
errorItemUrl.append(itemUrlList[j])
time.sleep(60)
def managerPage(pageUrlList, headers):
for i in range(0, len(pageUrlList)):
pageUrl = pageUrlList[i]
try:
itemUrlList = getItem(pageUrl, headers)
except Exception as e:
print 'item报错'
print e
time.sleep(30)
print '等待 30秒'
errorPageUrl.append(pageUrlList[i])
continue
print 'Items in page ' + str(i+1) + ' obtained.'
managerItem(itemUrlList)
|
调取上面的函数来实现爬取功能。若出现报错,则异常处理语句可保证脚本正常运行,同时自动等待一定时间。
7.主程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#start
print 'Start'
initialUrl = r'https://www.twinkl.com.cn/resources/age-group-3-5-mandarin-chinese-china-resources/communication-and-language-age-group-3-5-mandarin-chinese-china-resources/writing-communication-and-language-age-group-3-5-mandarin-chinese-china-resources'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = {'User-Agent': user_agent}
headers['cookie'] = 'XXXXX'
pageUrlList, pageNum = getUrl(initialUrl, headers)
print ' Page links obtained.'
errorPageUrl = []
errorItemUrl = []
managerPage(pageUrlList, headers)
if errorPageUrl:
print '重新开始下载失败页面'
managerPage(errorPageUrl, headers)
if errorItemUrl:
print '重新开始下载失败项目'
managerItem(errorItemUrl)
print 'All done!'
|
直接利用cookie模拟登陆。可重新开始下载报错的项目。
三、其他说明
- 经测试,整个脚本基本可以稳定运行,但是爬取到第八页之后经常出现报错现象;有时还有网络请求错误。有想法的朋友可以交流下。
- 这个脚本仅模拟手工操作过程,不会对服务器造成额外负担。