python爬蟲學習筆記-scrapy框架之start_url
在使用命令列建立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為"
以後每翻頁一次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
如果我們不做任何其他處理,則爬蟲啟動只會爬取"
因為這是一個列表形式,笨一點的方法就是把所有要爬取的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