序
距离上次发布已经过去2个月之多,最近总结的时间越来越少,只能挤着来总结了。终于花了点时间将页面分析与下载工作完成。
工程准备
既然是自动爬取,尽量使用Scrapy框架,Scrapy的安装和使用请自行查询和学习。
对于HTML解析,推荐使用BeautifulSoup, 查询定位页面元素很方便。
使用Scrapy创建一个新的工程。
scrapy startproject jiekespider
得到工程目录如下:
|--- scrapy.cfg
| |--- jiekespider
| | |--- __init__.py
| | |--- items.py
| | |--- pipelines.py
| | |--- settings.py
| | |--- spiders
| | | |---- __init__.py
| | | |---- jiekespider.py # 手动创建此爬虫文件
然后需要更改两个文件,setting.py和items.py
setting.py
找到ITEM_PIPELINES,然后添加文件流水包处理,启用文件保存功能:
# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'scrapy.contrib.pipeline.file.FilesPipeline': 1,
}
在最后添加文件存储位置:
FILES_STORE = "/home/bb/jike/"
item.py
Scrapy框架中item定义了要处理的页面的数据结构,比如学院课程信息包含:视频地址,课程名称,课程时长等:
添加LessonInfo类:
class LessonInfo(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
lesson = scrapy.Field()
unit = scrapy.Field()
time = scrapy.Field()
file_urls = scrapy.Field()
files = scrapy.Field()
然后是关键文件,jikespider.py:
#!python
# coding=utf-8
# import the necessary packages
from jikespider.items import *
import scrapy
from bs4 import BeautifulSoup
class JikeSpider(scrapy.Spider):
name = "jike-spider"
start_urls = ["http://www.jikexueyuan.com"]
dest_url = "http://ke.jikexueyuan.com/zhiye/ios/"
logined = 0
#继承与父类,默认会调用的回应处理
def parse(self, response):
if self.logined == 0:
#登陆处理
else:
#处理主页面
yield scrapy.Request(self.dst_url, self.parse_main)
如果单纯学习分析网页自动解析下载这部分功能足够了,使用这套框架只会把某一课程的第一节下载下来,因为不需要登陆验证。
登陆处理模块没有在此列出,但是前面章节已经做过演示及关键代码的展示,后续整理好后将放到Github上分享。
页面分析
入口页面
我们开始分析,首先确认页面入口,比如我使用了职业课程页面, 职业课程页面中选择IOS为启动URL,地址为:
http://ke.jikexueyuan.com/zhiye/ios/
。
查看源码可以看到从初级到高级的课程地址都分别在lesson-unit
下,其中我们关心的时实际课程的URL,例如:
<a jktag="0001|0.1|93004|acid:1225|levelid:19|stageid:49|slug:ios" target="_blank" href="http://www.jikexueyuan.com/course/1225.html" title="iOS 开发的前世今生" class="inner"> <span>1.iOS 开发的前世今生</span> <em class="tag"> </em> <i class="learn-state"></i> </a>
查看href项为http://www.jikexueyuan.com/course/1225.html
,class为inner
,则可将整个页面的课程URL查找出来:
def parse_main(self, response):
soup = BeautifulSoup(response.body, "lxml")
links = soup.find_all('a', class_="inner")
#遍历课程,转向下级分析
for link in links:
l = link['href']
yield scrapy.Request(l, self.parse_sub)
课程页面
现在可以进行分析课程页面http://www.jikexueyuan.com/course/1225.html
, 查看源码,可以分析出以下关键信息:
- 课程名称
- 子章节名称,课时及URL
子课程URL规律:
学院的单个课程的URL与第一个子章节的URL很像,例如http://www.jikexueyuan.com/course/1225.html
,第一章URL为http://www.jikexueyuan.com/course/1225_1.html?ss=1
,第2章URL为http://www.jikexueyuan.com/course/1225_2.html?ss=1
。所以可以考虑自己构建URL进行请求。
另外,分析可以看到子章节是在<div class='text-box'>块内,在一个text-box中会有课时的URL:
<div class="text-box">
<h2>
<a href="http://www.jikexueyuan.com/course/616_1.html?ss=1" jktag="&posArea=0007&posAOper=8035&posColumn=2877.1"> 课程介绍</a>
<p class="f_r">00:34</p>
</h2>
<p>本课时对课程内容做一简要介绍。</p>
</div>
那么子章节的URL就都可以从text-box中取出, 然后交给后面处理:
def parse_sub(self, response):
soup = BeautifulSoup(response.body, "lxml")
lesson_box = soup.find_all('div', class_='text-box')
for l in lesson_box:
sub_l = l.h2.a['href']
yield scrapy.Request(sub_l, self.parse_sub_info)
子章节页面
子章节页面类似这样的http://www.jikexueyuan.com/course/1225_1.html?ss=1
地址,到来关键的课程页面,重申以下,至此我们想要的视频才算是能解析了。
学院使用了video-js控件,并且支持HTML5的页面是包含source节点,这里浏览器看不到source节点就算了,还是抓取分析。
整个子课程页面只使用一个播放器,故我们只关心source节点。
另外课子章节名称和时长在text-box
块内,课程名在lesson-teacher
块内。
def parse_sub_info(self, response):
f_r = "00:00"
lesson = ""
unit = ""
file_urls = ""
id = ""
soup = BeautifulSoup(response.body, "lxml")
#查找子课程信息:时长和名称
lesson_box = soup.find_all('div', class_='text-box')
for l in lesson_box:
sub_l = l.h2.a['href']
print sub_l
if (response.url == sub_l): #因为多做了一次请求,只处理与本URL一致的课程信息
unit = l.h2.a.string
f_r = l.p.string
break
#查找课程视频地址
source = soup.find_all('source')
if len(source):
print source[0]['src']
file_urls = source[0]['src']
#查找课程名称
lesson_teacher = soup.find_all('div', class_='lesson-teacher')
if len(lesson_teacher) == 1:
lesson = lesson_teacher[0].div.h2.string
print lesson
#子课程名添加编号,方便按顺序查看
id = re.findall("\_(.*)\.html", response.url)
if len(id):
unit = id[0] + unit
#将课程信息推送到LessonInfo 处理单元(Item),后续保存和下载由Scapy自动完成
if len(file_urls) and len(lesson):
# yield the result
yield LessonInfo(lesson=lesson, unit=unit, time=f_r, file_urls=[file_urls])
运行测试
使用scrapy crawl jike-spider -o video.json
即将jike-spider初始化运行,-o为制定输出结果文件。
运行后Scrapy会将整个IOS课程子章节分别下载到设定的目录中,并且在当前目录生成抓取结果文件video.json。
如下内容:
[{"files": [{"url": "http://cv4.jikexueyuan.com/a561bd397474a8449303864d26cfd96b/201601221755/ios/course_1225/01/video/c1225b_01_h264_sd_960_540.mp4", "path": "full/f165d19a878d1048df08f6cc9ba389ee1480bcf6.mp4", "checksum": "357b934b62b2f564bdfff1628f5b180c"}], "lesson": "iOS \u5f00\u53d1\u7684\u524d\u4e16\u4eca\u751f", "unit": "1 iOS \u5f00\u53d1\u7684\u524d\u4e16\u4eca\u751f", "file_urls": ["http://cv4.jikexueyuan.com/a561bd397474a8449303864d26cfd96b/201601221755/ios/course_1225/01/video/c1225b_01_h264_sd_960_540.mp4"], "time": "06:02"},
{"files": [{"url": "http://cv4.jikexueyuan.com/c9c10ce5ea141d5b0b347ef12b4bbf94/201601221755/course/1701-1800/1782/video/4830_b_h264_sd_960_540.mp4", "path": "full/3cfc51a0ec32349df5113d014fe2e64705af84e1.mp4", "checksum": "561e26ff5fe6e209726c077734fb4df0"}], "lesson": "\u5229\u7528 Map Kit \u521b\u5efa\u5730\u56fe\u5e76\u6dfb\u52a0\u81ea\u5b9a\u4e49\u6807\u8bc6", "unit": "1 Map Kit \u57fa\u7840\u8bb2\u89e3", "file_urls": ["http://cv4.jikexueyuan.com/c9c10ce5ea141d5b0b347ef12b4bbf94/201601221755/course/1701-1800/1782/video/4830_b_h264_sd_960_540.mp4"], "time": "04:02"},
...
然后在设定的输出目录中可以看到文件在不断的下载,网速一般,可以喝杯咖啡了~