最近在学python,用的是2.7版,跟着网上的一些例子在学习,使用urllib和urllib2去抓取网页,然后学着学着就发现了scrapy这个爬虫框架。在这里记录一些东西,以免自己忘记。
最终目的是想爬取淘女郎网站上的每个mm主页(比如:田媛媛)下的所有展示图片。不过最后涉及到淘宝登录以及触发反爬虫策略,并没有成功。
最开始是看到这篇文章:Python爬虫实战四之抓取淘宝MM照片,用他的代码跑了一下,感觉很爽,可以用mm名称自动建目录,然后保存图片;但是爬了一会就报错了,当时没记录错误输出,但是大概意思是找不到response页面里的html代码,正则表达式匹配不到。通过一些了解,知道了原因:有些html代码是动态js产生的,而python代码没有去执行js,所以就获取不到。如果用chrome浏览器的审查元素能看到代码,但是直接打开网页源代码却没有的话,也是这个原因。
那么问题来了,怎么让爬虫也执行js?通过搜索,了解到selenium和PhantomJS。selenium是自动化测试经常用到的,python可以用它调用浏览器,模拟人的操作。因为调用浏览器是会执行js的,那么问题就应该可以解决了。然后发现效率真的很低,因为每次调用都要打开浏览器,等页面完全加载完成。PhantomJS是一个headless browser,意思是不需要界面,那么效率就应该提高很多了,然后发现selenium和PhantomJS已经整合在一起了,真是明智啊。
于是尝试在之前的代码中加入selenium和PhantomJS,开始爬图片,然而遇到另一个问题:访问mm主页需要淘宝登录。同时,图片又不需要通过js加载了,oh my god,是不是我之前眼花了。
然后又看了这篇文章:Python爬虫实战五之模拟登录淘宝并获取所有订单,于是尝试将登录的代码加入到前面。现在应该可以了吧,运行看看。靠,又遇到新问题了:登录是成功的,但是访问mm主页还是要登录,这是为啥?当时是不知道,就放弃了,后来了解到应该是和cookie有关。
接着,我安装了scrapy,学习了几个例子,也抓取了一些简单的网页,发现如果不需要登录,网页也没有动态js加载的问题,scrapy真是太好用了,代码量非常少就可以完成。然后还是不死心,想用scrapy尝试解决前面的问题。
items.py
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class TaobaommItem(scrapy.Item):
# define the fields for your item here like:
mm_name = scrapy.Field()
image_urls = scrapy.Field()
images = scrapy.Field()
上面的代码,是配置了需要抓取的mm姓名、图片url等,后面使用了ImagesPipeline
来下载图片,所以image_urls
和images
是必须的。
settings.py
# -*- coding: utf-8 -*-
BOT_NAME = 'taobaomm'
SPIDER_MODULES = ['taobaomm.spiders']
NEWSPIDER_MODULE = 'taobaomm.spiders'
ITEM_PIPELINES = {'taobaomm.tb_pipelines.getmmimgPipeline': 1}
IMAGES_STORE = '/vagrant/taobaomm/images'
USER_AGENT_LIST = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
HTTP_PROXY = 'http://127.0.0.1:8123'
DOWNLOADER_MIDDLEWARES = {
'taobaomm.middlewares.RandomUserAgentMiddleware': 400,
'taobaomm.middlewares.ProxyMiddleware': 410,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None
# Disable compression middleware, so the actual HTML pages are cached
}
在上面的代码,配置了自定义的getmmimgPipeline
,继承自ImagesPipeline
;另外,使用了下载中间件,主要作用是使用随机的ua和ip去访问,后面会提到。
tb_pipelines
# -*- coding: utf-8 -*-
from scrapy.pipelines.images import ImagesPipeline
from scrapy import Request
import hashlib
# import sys
# reload(sys)
# sys.setdefaultencoding('gbk')
class getmmimgPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
print item['mm_name'] + '*'*50
# 因为在下面的file_path方法中获得不到mm的姓名,所以在这里把mm的姓名作为meta传过去
return [Request(image_url, meta={'mm_name': item['mm_name']}) for image_url in item['image_urls']]
def file_path(self, request, response=None, info=None):
image_guid = hashlib.sha1(request.url).hexdigest()
path = 'full/%s/%s.jpg' % (request.meta['mm_name'], image_guid)
return path
在以上代码,重写了get_media_requests()
和file_path()
方法,作用是获取mm_name,然后保存图片的时候将保存在各自的子目录下。
example.py
# -*- coding: utf-8 -*-
import scrapy
from taobaomm.items import TaobaommItem
import time
import os
from selenium import webdriver
from taobaomm.logintotaobao import loginToTaobao
# import sys
# reload(sys)
# sys.setdefaultencoding('gbk')
class ExampleSpider(scrapy.Spider):
name = "example"
#allowed_domains = ["mm.taobao.com"]
start_urls = (
"https://mm.taobao.com/json/request_top_list.htm",
)
pageindex = 1
#taobao = loginToTaobao()
#cookiejar = taobao.loginToTaobao()
def parse(self, response):
for sel in response.xpath('//div[@class="list-item"]'):
name = sel.xpath(
'.//a[@class="lady-name"]/text()').extract()[0]
print u'美眉姓名:', name
self.mkdir('images/full/%s' % (name))
item = TaobaommItem()
item['mm_name'] = name
href = sel.xpath('.//a[@class="lady-avatar"]/@href').extract()
url = response.urljoin(href[0])
yield scrapy.Request(url,
meta={
#'cookiejar': response.meta['cookiejar'],
'item': item},
callback=self.parse_mm_page)
print u'去美眉图片页抓图:', url
#time.sleep(10)
self.pageindex += 1
next_page = self.start_urls[0] + '?page=' + str(self.pageindex)
yield scrapy.Request(next_page,
#meta={'cookiejar': response.meta['cookiejar']},
callback=self.parse)
#time.sleep(60)
def parse_mm_page(self, response):
print u'开始下载图片'
item = response.meta['item']
sel = response.xpath('//div[@class="mm-aixiu-content"]')
items = []
for href in sel.xpath('.//img/@src').extract():
items.append('http:' + href)
item['image_urls'] = items
return item
# 创建新目录
def mkdir(self, path):
path = path.strip()
# 判断路径是否存在
# 存在 True
# 不存在 False
isExists = os.path.exists(path)
# 判断结果
if not isExists:
# 如果不存在则创建目录
print u"偷偷新建了名字叫做", path, u'的文件夹'
# 创建目录操作函数
os.makedirs(path)
return True
else:
# 如果目录存在则不创建,并提示目录已存在
print u"名为", path, '的文件夹已经创建成功'
return False
关于淘宝登录的代码,这里就不贴出来了,采用了别人的,主要是这两篇:
执行scrapy,先登录淘宝
taobao = loginToTaobao()
cookiejar = taobao.loginToTaobao()
以上example.py代码是爬虫的实现,因为创建项目的时候用了scrapy startproject example
,所以文件名叫example.py,呵呵。
我的想法是这样的:先请求一级页面,然后抓取里面的第一个mm姓名和主页url,主页url就是二级页面,然后请求二级页面,抓取里面的所有图片,然后下载。然后再在一级页面获取第二个mm姓名和主页url,继续。。。
我发现执行的时候是这样的:scrapy不会获取了第一个mm,就马上去请求她的主页,而是会阻塞住,接着获取第二个mm,在一级页面循环获取到所有mm姓名和主页url后,然后统一交给scrapy调度,接着却不一定从第一个mm开始访问二级页面。这样就存在一个问题:scrapy的调度会很快触发淘宝反爬虫策略,会302重定向到sec.taobao.com的页面去,要求输入验证码,或者302重定向到login.taobao.com,提示输入登录密码。
另一个要说的,是cookie传递的问题,登录淘宝后,我使用cookiejar = taobao.loginToTaobao()
将cookie传入scrapy,一定要从第一个请求:start_requests()
方法产生的一级页面请求时就加入cookie(虽然一级页面不需要cookie),比如是这样:
def start_requests(self):
taobao = loginToTaobao()
cookiejar = taobao.loginToTaobao()
yield scrapy.Request(self.url,
meta={'cookiejar': cookiejar},
callback=self.parse_list
)
这里和example.py里面的代码不同,只是说明我曾经尝试过。
在请求二级页面时,需要接收前面的cookie,用meta={'cookiejar': response.meta['cookiejar']}
。如果不这样的话,会要求登录。
其实我也不确定是对的,因为在抓取图片时,是另一个域名,cookie带不过去,也不用传。
middlewares.py
# -*- coding: utf-8 -*-
import os
import random
from scrapy.conf import settings
class RandomUserAgentMiddleware(object):
def process_request(self, request, spider):
ua = random.choice(settings.get('USER_AGENT_LIST'))
if ua:
request.headers.setdefault('User-Agent', ua)
class ProxyMiddleware(object):
def process_request(self, request, spider):
request.meta['proxy'] = settings.get('HTTP_PROXY')
以上代码是取自这篇文章:
其实,scrapy本身也有模拟登录的方法,叫scrapy.FormRequest.from_response()
,我也做过尝试,还未成功。
综上所述,主要问题还是集中在:登录、触发反爬虫策略、验证码(目前只能手动输入)、302重定向如何处理。我还在尝试中。