在使用命令行创建scrapy项目后,会发现在spider.py文件内会生成这样的代码:
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
其中比较好理解的是name,这个字段代表爬虫项目名称,在命令行创建时已经指定,allowed_domains代表允许爬取的域名,这两个字段均在创建时已经设置好,不需要更改,但比较特殊的是start_url这个字段,它也是一个列表形式,官方文件的解释为包含spider在启动时爬取的url列表,用于定义初始请求。这样就可以类比如果不使用scrapy框架,url应该如何定义;
例如爬取豆瓣读书时发现首页url为"https://book.douban.com/top250?start=0",
以后每翻页一次start字段值递增25,这样就可以很容易用一个循环做到url的构建,再比如如果爬取今日头条图片,分析Ajax后首页url为“https://www.toutiao.com/search_content/?offset=0&format=json&keyword=街拍&autoload=true&count=20&cur_tab=1&from=search_tab&pd=synthesis”,以后每页更改其中offset参数递增20,则可以利用urlencode()方法构建url。
其实scrapy的start_url构建方法与普通爬虫的构建本质上没有任何区别,只是因为在框架的调度使其实现更加容易。
下面以http://images.so.com网站为例:
创建好项目文件后,spider.py内的代码如下:
class ImagesSpider(Spider):
name = 'images'
allowed_domains = ['images.so.com']
start_urls = ['http://images.so.com/']
def prase(self):
pass
如果我们不做任何其他处理,则爬虫启动只会爬取"http://images.so.com/"上的内容。
因为这是一个列表形式,笨一点的方法就是把所有要爬取的url添加到列表中,然后遍历就可以了:
start_urls = [
'http://images.so.com/z?ch=beauty',
'http://images.so.com/z?ch=wallpaper']
for url in start_urls:
yield scrapy.Request(url=url, callback=self.parse)
这样当然没错,而且还很好理解,但未免显得太实在了。既然要返回一组列表,自然而然就可以用到以前的构建方法,构建代码如下:
def start_requests(self):
data={'ch':'photography','listtype':'new'}
base_url='https://image.so.com/zj?'
for page in range(1,self.settings.get('MAX_PAGE')+1):
data['sn']=page*30
params=urlencode(data)
url=base_url+params
yield Request(url=url,callback=self.parse)
这里的url是分析Ajax得到的,可以看出构建方法没有什么特别之处,这个方法返回的Request请求,而回调函数就是我们解析html的方法,爬虫运行时,不断的将请求生成并被parse()方法调用解析,parse()方法代码如下:
def parse(self, response):
result=json.load(response.text)
for image in result.get('list'):
item=Images360Item()
item['id']=image.get('imageid')
item['url']=image.get('qhimg_url')
item['title']=image.get('group_title')
item['thumb']=image.get('qhimg_thumb_url')
yield item
当涉及到不止一层关系的爬虫,例如微博的爬取,爬取了微博首页后,还要爬取它的关注,粉丝,微博列表的情况下,就相对复杂的多了。但其实本质是一样的,只是下一层的start_url的构建不是在start_request()方法中,而是在第一层罗辑的解析方法中生成,伪代码如下,以微博为例:
class WeibocSpider(Spider):
name = 'weiboc'
allowed_domains = ['m.weibo.cn']
user_url=''
follow_url=''
fan_url=''
weibo_url=''
start_user=['3217179555']
def start_requests(self):
for uid in self.start_user:
yield Request(self.user_url.format(id=uid),callback=self.parse_user)
def parse_user(self,response):
self.logger.debug(response)
result=json.loads(response.text)
if result.get('data').get('userInfo'):
user_Info=result.get('data').get('userInfo')
user_item=UserItem()
field_map={}
for field ,attr in field_map.items():
user_item[field]=user_Info.get['attr']#返回用户信息
#关注
uid=user_Info.get('id')
#构造下一页链接,并返回Request
yield Request(self.follow_url.format(uid=uid,page=1),callback=self.parse_follows,meta={'page':1,'uid':uid})
#fans
yield Request(self.fan_url.format(uid=uid,page=1),callback=self.parse_fans,meta={'page':1,'uid':uid})
#weibo
yeild Request(self.weibo_url.format(uid=uid,page=1),callback=self.parse_weibos,meta={'page':1,'uid':uid})
def parse_follows(self,response):
"""
解析用户关注
:param response: Response对象
:return:
"""
result=json.loads(response.text)
if result.get('ok') and result.get('data').get('cards') and len(result.get('data').get('cards'))
and result.get('data').get('cards')[-1].get('card_group'):
#解析关注列表每个用户信息并发起新的解析请求
follows=result.get('data').get('cards')[-1].get('card_group')
for folow in follows:
if follow.get('user'):
uid=follow.get('user').get('id')
yield Request(self.user_url.format(uid=uid)callback=self.parse_user())
#关注列表
uid=response.meta.get('uid')
user_relation_item=UserRelationItem()
follows=[{'id':follow.get('user').get('id'),'name':follow.get('user').get('screen_name')}for follow in follows]
user_relation_item['id']=uid
user_relation_item['follows']=follows
user_relation_item['fans']=[]
yield user_relation_item
#提取下一页的关注
page=response.meta.get('page')+1
yield Request(self.follow_url.format(uid=uid,page=page),callback=self.parse_follows,meta={'page':page.'uid':uid})
这里首先通过start_request()方法生成要爬取的微博首页请求,在parse_users()方法解析得到用户信息后,再生成下一层罗辑需要的请求列表,这样就可以实现一层层不断的爬取,貌似用到了深度优先的概念。
ps:GitHub地址:https://github.com/linl12138/image