PhantomJS 是一个基于 WebKit 的服务器端 JavaScript API。它全面支持web而不需浏览器支持,其快速、原生支持各类Web标准:DOM 处理、CSS 选择器、JSON、Canvas 和 SVG。 PhantomJS 能够用于页面自动化、网络监测、网页截屏以及无界面测试等。css
http://phantomjs.org/download.htmlhtml
直接利用 pip 安装便可python
pip install pyspider
测试web
若是安装过程没有提示任何错误,那就证实一些OK。json
命令行输入数组
pyspider all
而后浏览器访问 http://localhost:5000浏览器
观察一下效果,若是能够正常出现 PySpider 的页面,那证实一切OK服务器
将PhantomJS放到C:\Python27\Scripts中,并将可执行程序文件所在位置以及C:\Python27\Scripts添加到环境变量的Path中网络
官方文档:http://docs.pyspider.org/en/latest/python爬虫
1)首先在scripts文件夹下打开cmd,输入:
pyspider all
这句命令的意思是,运行 pyspider 并 启动它的全部组件,包括PhantomJS
2)接下来在浏览器中输入 http://localhost:5000,能够看到 PySpider 的主界面,点击右下角的 Create,命名为 taobaomm,固然名称你以随意取,继续点击 Create。
整个页面分为两栏,左边是爬取页面预览区域,右边是代码编写区域。下面对区块进行说明:
左侧绿色区域:这个请求对应的 JSON 变量,在 PySpider 中,其实每一个请求都有与之对应的 JSON 变量,包括回调函数,方法名,请求连接,请求数据等等。
绿色区域右上角Run:点击右上角的 run 按钮,就会执行这个请求,能够在左边的白色区域出现请求的结果。
左侧 enable css selector helper: 抓取页面以后,点击此按钮,能够方便地获取页面中某个元素的 CSS 选择器。
左侧 web: 即抓取的页面的实时预览图。
左侧 html: 抓取页面的 HTML 代码。
左侧 follows: 若是当前抓取方法中又新建了爬取请求,那么接下来的请求就会出如今 follows 里。
左侧 messages: 爬取过程当中输出的一些信息。
右侧代码区域: 你能够在右侧区域书写代码,并点击右上角的 Save 按钮保存。
右侧 WebDAV Mode: 打开调试模式,左侧最大化,便于观察调试。
3)开始爬取
https://mm.taobao.com/json/request_top_list.htm?page=1,其中 page 参数表明页码。因此咱们暂时抓取前 30 页。页码到最后能够随意调整。
首先定义基地址,而后定义爬取的页码和总页码。
from pyspider.libs.base_handler import * class Handler(BaseHandler): crawl_config = { } def __init__(self): self.baseurl = 'https://mm.taobao.com/json/request_top_list.htm?page=' self.page_num = 1 self.total_num = 30 @every(minutes=24 * 60) def on_start(self): while self.page_num <= self.total_num: url = self.baseurl + str(self.page_num) print url self.crawl(url, callback=self.index_page, validate_cert=False) self.page_num += 1 @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^="http"]').items(): self.crawl(each.attr.href, callback=self.detail_page, validate_cert=False) @config(priority=2) def detail_page(self, response): return { "url": response.url, "title": response.doc('title').text(), }
点击 save 保存代码,而后点击左边的 run,运行代码。
运行后会发现 follows 出现了 30 这个数字,说明咱们接下来有 30 个新请求,点击可查看全部爬取列表。另外控制台也有输出,将全部要爬取的 URL 打印了出来。
而后点击左侧任意一个绿色箭头,能够继续爬取这个页面。例如点击第一个 URL,来爬取这个 URL
4)HTTP 599: SSL certificate problem错误的解决方法
在 crawl 方法中加入忽略证书验证的参数,validate_cert=False
5)修改 index_page 方法
def index_page(self, response): for each in response.doc('a[href^="http"]').items(): self.crawl(each.attr.href, callback=self.detail_page, validate_cert=False)
其中 response 就是刚才爬取的列表页,response 其实就至关于列表页的 html 代码,利用 doc 函数,实际上是调用了 PyQuery,用 CSS 选择器获得每个MM的连接,而后从新发起新的请求。
好比,这里拿到的 each.attr.href 多是 mm.taobao.com/self/model_card.htm?user_id=687471686,在这里继续调用了 crawl 方法,表明继续抓取这个连接的详情。
而后回调函数就是 detail_page,爬取的结果会做为 response 变量传过去。detail_page 接到这个变量继续下面的分析。
右边的页面使用 JS 渲染生成的,而普通的抓取是不能获得 JS 渲染后的页面的,PySpider 提供了动态解析 JS 的机制。由于在前面装好了 PhantomJS,因此,这时候就轮到它来出场了。在最开始运行 PySpider 的时候,使用了pyspider all
命令,这个命令是把 PySpider 全部的组件启动起来,其中也包括 PhantomJS。
只是简单地加了一个 fetch_type='js',点击绿色的返回箭头,从新运行一下。
6)数据分析
继续修改 detail_page 方法,而后增长一个 domain_page 方法,用来处理每一个 MM 的个性域名。
先点击F12打开开发者选项,找到mm的个性域名模块
.mm-p-domain-info li > span指的是从域名地点的div标签到span标签的路径上的全部祖先节点。再加上”https:”就构成了个性域名的URL。以后利用self.crawl来继续对domain这个页面进行爬取。(CSS选择器详见python爬虫(7)--Beautiful Soup的用法)
def detail_page(self, response): domain = 'https:' + response.doc('.mm-p-domain-info li > span').text() print domain self.crawl(domain, callback=self.domain_page) def domain_page(self,response): pass
继续从新 run,预览一下页面,看到了 MM 的全部图片。
7)保存
完善 domain_page 代码,实现保存简介和遍历保存图片的方法。
在这里,PySpider 有一个特色,全部的 request 都会保存到一个队列中,并具备去重和自动重试机制。因此,最好的解决方法是,把每张图片的请求都写成一个 request,而后成功后用文件写入便可,这样会避免图片加载不全的问题。
import os class Deal: def __init__(self): self.path = DIR_PATH if not self.path.endswith('/'): self.path = self.path + '/' if not os.path.exists(self.path): os.makedirs(self.path) def mkDir(self, path): path = path.strip() dir_path = self.path + path exists = os.path.exists(dir_path) if not exists: os.makedirs(dir_path) return dir_path else: return dir_path def saveImg(self, content, path): f = open(path, 'wb') f.write(content) f.close def saveBrief(self, content, dir_path, name): file_name = dir_path + "/" + name + ".txt" f = open(file_name, "w+") f.write(content.encode('utf-8')) def getExtension(self, url): extension = url.split('.')[-1] return extension
这里面包含了四个方法。
mkDir:建立文件夹,用来建立 MM 名字对应的文件夹。
saveBrief: 保存简介,保存 MM 的文字简介。
saveImg: 传入图片二进制流以及保存路径,存储图片。
getExtension: 得到连接的后缀名,经过图片 URL 得到。
而后在 domain_page 中具体实现以下
def domain_page(self,response): name = response.doc('.mm-p-model-info-left-top dd > a').text() dir_path = self.deal.mkDir(name) brief = response.doc('.mm-aixiu-content').text() if dir_path: imgs = response.doc('.mm-aixiu-content img').items() count = 1 self.deal.saveBrief(brief, dir_path, name) for img in imgs: url = img.attr.src if url: extension = self.deal.getExtension(url) file_name = name + str(count) + '.' + extension count += 1 self.crawl(img.attr.src, callback=self.save_img, save={'dir_path':dir_path, 'file_name':file_name}, validate_cert=False) def save_img(self, response): content = response.content dir_path = response.save['dir_path'] file_name = response.save['file_name'] file_path = dir_path + '/' + file_name self.deal.saveImg(content, file_path)
以上方法首先获取了页面的全部文字,而后调用了 saveBrief 方法存储简介。
而后遍历了 MM 全部的图片,并经过连接获取后缀名,和 MM 的姓名以及自增计数组合成一个新的文件名,调用 saveImg 方法保存图片。
完善代码以下:
#!/usr/bin/env python # -*- encoding: utf-8 -*- # Created on 2018-01-24 17:33:28 # Project: taobaomm from pyspider.libs.base_handler import * DIR_PATH = '/var/py/mm' class Handler(BaseHandler): crawl_config = { } def __init__(self): self.baseurl = 'https://mm.taobao.com/json/request_top_list.htm?page=' self.page_num = 1 self.total_num = 30 self.deal = Deal() @every(minutes=24 * 60) def on_start(self): while self.page_num <= self.total_num: url = self.baseurl + str(self.page_num) print url self.crawl(url, callback=self.index_page, validate_cert=False) self.page_num += 1 @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^="http"]').items(): self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js', validate_cert=False) @config(priority=2) def detail_page(self, response): domain = 'https:' + response.doc('.mm-p-domain-info li > span').text() print domain self.crawl(domain, callback=self.domain_page, validate_cert=False) def domain_page(self,response): name = response.doc('.mm-p-model-info-left-top dd > a').text() dir_path = self.deal.mkDir(name) brief = response.doc('.mm-aixiu-content').text() if dir_path: imgs = response.doc('.mm-aixiu-content img').items() count = 1 self.deal.saveBrief(brief, dir_path, name) for img in imgs: url = img.attr.src if url: extension = self.deal.getExtension(url) file_name = name + str(count) + '.' + extension count += 1 self.crawl(img.attr.src, callback=self.save_img, save={'dir_path':dir_path, 'file_name':file_name}, validate_cert=False) def save_img(self, response): content = response.content dir_path = response.save['dir_path'] file_name = response.save['file_name'] file_path = dir_path + '/' + file_name self.deal.saveImg(content, file_path) import os class Deal: def __init__(self): self.path = DIR_PATH if not self.path.endswith('/'): self.path = self.path + '/' if not os.path.exists(self.path): os.makedirs(self.path) def mkDir(self, path): path = path.strip() dir_path = self.path + path exists = os.path.exists(dir_path) if not exists: os.makedirs(dir_path) return dir_path else: return dir_path def saveImg(self, content, path): f = open(path, 'wb') f.write(content) f.close def saveBrief(self, content, dir_path, name): file_name = dir_path + "/" + name + ".txt" f = open(file_name, "w+") f.write(content.encode('utf-8')) def getExtension(self, url): extension = url.split('.')[-1] return extension