综述:先介绍开发环境,在介绍从豆瓣电影上面爬取电影的电影名称,电影信息,电影剧照等信息,最终效果是以电影名称为文件夹名,文件夹包含一个txt文件和许多张剧照。如下图所示1是电影名,2是电影信息,3是各种剧照
比较知名框架的是Scrapy 和PySpider。
PySpider | 上手更简单,操作更加简便,增加了 WEB 界面,开发效率高,集成了phantomjs,可以用来处理复杂的js渲染的页面。 |
Scrapy | 自定义程度高,比PySpider更底层一些,适合学习研究,需要学习的相关知识多,可以自己拿来研究分布式和多线程等等。 |
所以综上所述我选择的爬虫框架是PySpider。
2.1 安装pip
pip是python的包管理工具,类似RedHat里面的yum,通过pip可以快速下载安装python软件隐藏了背后复杂的下载安装的过程
下载地址:下载pip安装包
2.2 安装phantomjs
phantomjs是一个浏览器内核程序,可以在爬虫开发过程中,模拟浏览器运行js,简化js处理过程
下载地址:phantomjs官网
2.3 使用pip安装pyspider
pip install pyspider
2.4 运行pyspider
pyspider all
2.5 通过浏览器访问pyspider:http://localhost:5000
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2018-11-29 15:50:57
# Project: DBDY_01
from pyspider.libs.base_handler import *
import os;
class Handler(BaseHandler):
base_dir = "D:\\mov"
crawl_config = {
}
@every(minutes=24 * 60)
def on_start(self):
self.crawl('https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%BB%8F%E5%85%B8&sort=rank&page_limit=30&page_start=0',
validate_cert=False,
callback=self.index_page)
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
marr = response.json["subjects"]
//最终数据的存储路径,经常要用到所以提取在最外面,方便使用
for m in marr:
mv_id=m["id"]
self.crawl(m["url"],
validate_cert=False,
callback=self.mv_page,
save=mv_id)
@config(age=10 * 24 * 60 * 60)
def mv_page(self, response):
#电影名
mv_name = response.doc("h1 > span").text().encode("gbk")
#电影描述
mv_desc = response.doc("#info").text().encode("gbk")
#创建该电影的文件夹
mv_dir = self.base_dir+"/"+mv_name
if not os.path.exists(mv_dir):
os.makedirs(mv_dir)
#创建描述信息写入文件
mv_file = open(mv_dir+"/"+mv_name+".txt","w")
mv_file.write(mv_desc)
mv_file.flush()
mv_file.close()
#爬取剧照页面
all_photos_url = "https://movie.douban.com/subject/"+response.save+"/all_photos"
self.crawl(all_photos_url,
validate_cert=False,
callback=self.all_photos_page,
save=mv_dir)
@config(age=10 * 24 * 60 * 60)
def all_photos_page(self, response):
for x in response.doc(".article a").items():
if re.match("^.*\\d+/$",x.attr.href)
self.crawl(x.attr.href,
validate_cert=False,
callback=self.img_page,
save=response.save)
@config(age=10 * 24 * 60 * 60)
def img_page(self, response):
#图片url
img_url = response.doc(".mainphoto > img").attr.src
img_name =img_url.split("/")[-1]
self.crawl(img_url,
validate_cert=False,
callback=self.down_img_page,
save=response.save+"\\"+img_name )
@config(age=10 * 24 * 60 * 60)
def down_img_page(self, response):
//拿到图片数据
img_data = response.content
img_file = open(response.save,"wb")
img_file.write(img_data)
img_file.flush()
img_file.close()
看首页地址(有许多电影的展示页面):
https://movie.douban.com/explore#!type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0
我们可以进行分析:当我们尝试改变page_limit数值变成100时,发现页面条数并没有变成100条,而只有在点击加载更多的时候才会加载出下一页,就此推测这儿应该用的是Ajax异步调用,那么我们接着从调试器查找触发源头:xhr(代表ajax),可以找到他的访问url地址:https://movie.douban.com/j/search_subjects?type=movie&tag=%E9%9F%A9%E5%9B%BD&sort=recommend&page_limit=20&page_start=0
分析:j:代表json
我们试着访问这个路径,发现是许多json,有subjects对象,里面有许多电影信息。
我随机选择了一个json进行展示
"subjects":{ //对象
"rate":"6.5", //评分
"cover_x":896,
"title":"女警", //电影名称
"url":"https:\/\/movie.douban.com\/subject\/30204142\/", //该电影的具体内容
"playable":false, //是否可以播放
"cover":"https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2553964373.webp", //电影的展示图片
"id":"30204142", //电影编号
"cover_y":1280,
"is_new":true //是否是一个新电影
}
根据以上分析我们得到结论:这个url:https://movie.douban.com/j/search_subjects?type=movie&tag=%E9%9F%A9%E5%9B%BD&sort=recommend&page_limit=20&page_start=0,可以作为我们爬取数据的一个入口
开始写下面代码:
@every(minutes=24 * 60)
def on_start(self):
self.crawl('https://movie.douban.com/j/search_subjects?type=movie&tag=%E9%9F%A9%E5%9B%BD&sort=recommend&page_limit=20&page_start=0'
,validate_cert=False
, callback=self.index_page)
on_start(self):开始入口
self.crawl('url' ,validate_cert=False , callback=self.index_page)
然后开始爬去这个地址的信息:self.crawl('地址')
callback=self.index_page ——爬完地址触发回调方法到index_page 方法
validate_cert=False :忽略安全校验,
我们会发现后面就有一个def index_page(self, response) 这个方法,这个方法的response就是对刚刚地址爬取完的响应对象
我们可以只在里面写一个pass,来执行一下我们写的程序,判断是否正确,运行结果:出现我们想要的所有json,说明访问时成功的,接着我们开始自己的业务需求,具体代码如下:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
marr = response.json["subjects"]
for m in marr:
self.crawl(m["url"],validate_cert=False, callback=self.mv_page)
response.json["subjects"]:拿到这个subjects对象所有的json,得到一个数组,遍历这个数组,拿到的是一个对象,这个对象就是我们的每一个电影的json,此时可以拿到这个对象的url属性,-url:https:\/\/movie.douban.com\/subject\/30204142\/,进行爬取数据,我们试着访问这个url:
我们可以发现这个地址就是某一个电影的主页
然后开始爬取这个地址,回调:def mv_page(self, response)方法
@config(age=10 * 24 * 60 * 60)
def mv_page(self, response):
#电影名
mv_name = response.doc("h1 > span").text().encode("gbk")
#电影描述
mv_desc = response.doc("#info").text().encode("gbk")
#创建该电影的文件夹
mv_dir = self.base_dir+"/"+mv_name
if not os.path.exists(mv_dir):
os.makedirs(mv_dir)
#创建描述信息写入文件
mv_file = open(mv_dir+"/"+mv_name+".txt","w")
mv_file.write(mv_desc)
mv_file.flush()
mv_file.close()
#爬取剧照页面
all_photos_url = "https://movie.douban.com/subject/"+response.save+"/all_photos"
self.crawl(all_photos_url,
validate_cert=False,
callback=self.all_photos_page,
save=mv_dir)
在def mv_page(self, response)方法:需要在当前的页面元素删选出我们想要的电影名称,电影信息
response.doc()-可以接收css选择器,通过选择器来查找自己需要的元素(在编辑页面:有enable css selector helper,点击就可以选择自己想要元素对应的css)
.text().encode("gbk") 在得到容器后需要拿到里面内容,调用.text,window版本默认是gbk的,不调用会报错,乱码
根据以上内容我们就可以得到自己想要的元素,之后把需要内容就行存储在本地。
自己在本地建立一个文件夹,将路径写在:base_dir,这个路径经常会用到,所以我们把它提取到外面,来进行引用。
至此,我们的目标已完成2/3,只剩下剧照没有爬取。
爬取剧照:发现它的css选择器嵌套在span标签,无法单独根据css标签拿到剧照路径,下一步应该在调试器获取这个地址,进行删选
全部剧照的地址:subject/30204142/all_photos">图片52
我们分析这个url发现:它的标红是电影编号,那么我们可以根据电影编号自己拼出这个地址
我们可以在index_page方法得到的电影编号,而使用它mv_page这个方法,他的传递用的是:
现在index_page方法拿到id,在回调函数中补充:save=mv_id,那么在mv_page这个方法就可以用response.save获取id
在获取到id后,我们就可以将其拼接在地址,从而进行爬取,触发回调方法:all_photos_page
进行测试,我们确实可以爬取到下图信息
我们来分析这个网页,当我们点开全部剧照图片时,发现我们不是要这些小图片,而是点开每个图片的大图片,而我们想要的图片是被一个超链接给包起来了,所以我们需要得到这个超链接并且进行删选
这个url找不到它对应的css选择器,所以我们可以稍微往上一级走一点,拿到所有url后,我们进行遍历(.items),此时拿到所有的连接,但是我们仔细观察,发现并不是所有的连接格式都正确,我们只要删选最后是以数字结尾就可以,用正则进行匹配
@config(age=10 * 24 * 60 * 60)
def all_photos_page(self, response):
for x in response.doc(".article a").items():
if re.match("^.*\\d+/$",x.attr.href)
self.crawl(x.attr.href,
validate_cert=False,
callback=self.img_page,
save=response.save)
最后过滤好的url:https://movie.douban.com/photos/photo/2552999044/,即单个剧照的大图
现在我们的目标是:从这个页面中拿到这个图片的地址:
@config(age=10 * 24 * 60 * 60)
def img_page(self, response):
#图片url
img_url = response.doc(".mainphoto > img").attr.src
self.crawl(img_url,
validate_cert=False,
callback=self.down_img_page,
)
我们爬取这个地址后拿到的是图片的二进制,需要拿到这个图片的内容
@config(age=10 * 24 * 60 * 60)
def down_img_page(self, response):
//拿到图片数据
img_data = response.content
img_file = open(response.save["mv_img_path"],"wb")
img_file.write(img_data)
img_file.flush()
img_file.close()
现在问题是文件存储路径:目标是存在每一个电影文件夹下面,
为了防止存储的剧照图片重名,那么原来人家的剧照名称是什么现在也必须是什么
在路径的最后是图片的名称: img_name =img_url.split("/")[-1]
路径:在def mv_page写save=mv_dir,在all_photos_page用save=response.save,在img_page用save=response.save+"\\"+img_name,那么在down_img_page中路径就用response.save得到剧照的路径
至此全部完成,一篇教学文章花费了很久,自己操作很快,但是这篇教学就比较繁琐,不过也不负众望,搞定这篇实战案例
有什么好的想法大家可以评论