熟悉Scrapy以後,本篇文章帶你們爬取七麥數據(https://www.qimai.cn/rank )的ios appstore付費應用排行榜前100名應用。html
爬取內容包括app在列表中的下標,app圖標地址,app的名稱信息,app的類型,在分類中的排行,開發者,詳情等。node
考慮的問題:python
在須要放置項目的目錄下,ios
> scrapy startproject qimairank
回車便可建立默認的Scrapy項目架構。git
建立Item來存儲咱們爬取的app在列表中的下標,app圖標地址,app的名稱信息,app的類型,在分類中的排行,開發者,詳情。
修改items.py
,在下面增長github
class RankItem(scrapy.Item): # 下標 index = scrapy.Field() # 圖標地址 src = scrapy.Field() # app標題信息 title = scrapy.Field() # app類型 type = scrapy.Field() # 分類中的排行 type_rank = scrapy.Field() # 開發者 company = scrapy.Field() # 詳情信息 info = scrapy.Field()
在spiders
目錄下建立RankSpider.py
,並建立class RankSpider
,繼承於scrapy.Spider。web
import scrapy class RankSpider(scrapy.Spider): name = "RankSpider" start_urls = ["https://www.qimai.cn/rank"] def parse(self, response): pass
name
:用於區別Spider,該名字必須是惟一的。start_urls
:Spider在啓動時進行爬取的url列表,首先會爬取第一個。def parse(self, response)
:獲得url的response信息後的解析方法。解析用的Selectors選擇器有多種方法:正則表達式
下面咱們用xpath()選擇節點,xpath的語法可參考w3c的http://www.w3school.com.cn/xp... 學習,須要熟悉語法、運算符、函數等。json
def parse(self, response): base = response.xpath( "//div[@class='ivu-row rank-all-item']/div[@class='ivu-col ivu-col-span-8'][2]//ul/li[@class='child-item']/div[@class='ivu-row']") for box in base: # 建立實例 rankItem = RankItem() # 下標 rankItem['index'] = \ box.xpath(".//div[@class='ivu-col ivu-col-span-3 left-item']/span/text()").extract()[0] # 圖標地址 rankItem['src'] = box.xpath(".//img/@src").extract()[0] # app名稱信息 rankItem['title'] = box.xpath(".//div[@class='info-content']//a/text()").extract()[0] # app類型 rankItem['type'] = box.xpath(".//div[@class='info-content']//p[@class='small-txt']/text()").extract()[0] # 分類中的排行 rankItem['type_rank'] = box.xpath( ".//div[@class='info-content']//p[@class='small-txt']//span[@class='rank-item']/text()").extract()[ 0] # 開發者 rankItem['company'] = box.xpath( ".//div[@class='info-content']//p[@class='small-txt']//span[@class='company-item']/text()").extract()[ 0] # 詳情頁地址 infoUrl = "https://www.qimai.cn" + box.xpath(".//div[@class='info-content']//a/@href").extract()[0] yield rankItem
直接運行
qimairank>scrapy crawl RankSpider -o data.json
你會發現窗口沒有item輸出,data.json中也沒有數據,是咱們寫錯了嗎?
scrapy默認遵照robot協議的,在訪問網址前會先訪問robot.txt來查看本身是否有權限訪問。若是網站不容許被爬,就不能訪問。
怎麼樣不遵照協議呢?
settings.py # Obey robots.txt rules ROBOTSTXT_OBEY = False
再次運行仍然失敗,咱們來看下具體緣由:
由於七麥網站對請求的User-Agent
作了校驗,解決辦法是在配置文件
settings.py # Enable or disable downloader middlewares # See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html DOWNLOADER_MIDDLEWARES = { # 'qimairank.middlewares.QimairankDownloaderMiddleware': 543, 'qimairank.middlewares.RandomUserAgent': 1, } USER_AGENTS = [ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)", "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20", "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", ]
並在middlewares.py
中建立RandomUserAgent
import random class RandomUserAgent(object): """ 隨機獲取settings.py中配置的USER_AGENTS設置'User-Agent' """ def __init__(self, agents): self.agents = agents @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('USER_AGENTS')) def process_request(self, request, spider): request.headers.setdefault('User-Agent', random.choice(self.agents))
再次運行,沒有報錯,可是沒有數據,是咱們的xpath寫錯啦?咱們在parse中增長輸出body的信息
能夠看到body爲空,沒有咱們須要的列表數據,這是由於七麥數據是經過js動態渲染的,在渲染完成前,咱們的response已經返回,那麼怎麼樣才能等一等呀,等到渲染完成才返回呢?
爬取動態渲染的方式,我知道是經過Splash或者Selenium,像咱們的桌面版系統能夠選擇用Selenium,操做能夠設置可視化,全部界面操做都能看見,Splash依賴於Docker,無界面。
安裝Selenium包:
pip install selenium
使用前須要安裝驅動,配置詳情點擊
驅動安裝完成,在middlewares.py中建立 SeleniumMiddleware
class SeleniumMiddleware(object): def __init__(self): self.timeout = 50 # 2.Firefox--------------------------------- # 實例化參數對象 options = webdriver.FirefoxOptions() # 無界面 # options.add_argument('--headless') # 關閉瀏覽器彈窗 options.set_preference('dom.webnotifications.enabled', False) options.set_preference('dom.push.enabled', False) # 打開瀏覽器 self.browser = webdriver.Firefox(firefox_options=options) # 指定瀏覽器窗口大小 self.browser.set_window_size(1400, 700) # 設置頁面加載超時時間 self.browser.set_page_load_timeout(self.timeout) self.wait = WebDriverWait(self.browser, self.timeout) def process_request(self, request, spider): # 當請求的頁面不是當前頁面時 if self.browser.current_url != request.url: # 獲取頁面 self.browser.get(request.url) time.sleep(5) else: pass # 返回頁面的response return HtmlResponse(url=self.browser.current_url, body=self.browser.page_source, encoding="utf-8", request=request) def spider_closed(self): # 爬蟲結束 關閉窗口 self.browser.close() pass @classmethod def from_crawler(cls, crawler): # 設置爬蟲結束的回調監聽 s = cls() crawler.signals.connect(s.spider_closed, signal=signals.spider_closed) return s
在settins.py中配置
# Enable or disable downloader middlewares DOWNLOADER_MIDDLEWARES = { # 'qimairank.middlewares.QimairankDownloaderMiddleware': 543, 'qimairank.middlewares.RandomUserAgent': 1, 'qimairank.middlewares.SeleniumMiddleware': 10, }
再次運行scrapy crawl RankSpider -o data.json
,啦啦啦~這回有數據啦。
觀察爬取出來的data.json,發現怎麼肥四,只有20條數據,並且除了前6個的app圖標都是七麥的默認圖標。
這是由於七麥數據的列表默認每頁20條,並且默認渲染前6個的圖標,其他的頁須要觸發滑動事件加載,並且滑動到的圖標纔開始渲染。這樣怎麼辦呢?咱們只須要滑動到能夠加載的按鈕就能夠啦,檢查發如今三個列表的外層標籤有一個class爲cm-explain-bottom的標籤
咱們用Selenium調用js腳本,滑動到這個標籤就能夠啦,在中間件process_request方法更改
def process_request(self, request, spider): # 當請求的頁面不是當前頁面時 if self.browser.current_url != request.url: # 獲取頁面 self.browser.get(request.url) time.sleep(5) # 請求的url開始爲https://www.qimai.cn/rank/時,調用滑動界面,每頁20個,滑動4次 if request.url.startswith("https://www.qimai.cn/rank"): try: for i in (0, 1, 2, 3): self.browser.execute_script( "document.getElementsByClassName('cm-explain-bottom')[0].scrollIntoView(true)") time.sleep(4) except JavascriptException as e: pass except Exception as e: pass
再次執行scrapy crawl RankSpider -o data1.json
,則可看見已經生成data1.json裏面有100個item。
詳情頁須要跟進url,咱們在RankSpider#parse方法中,不用yield Item,而是yield Request就能夠跟進。
# 詳情頁地址 infoUrl = "https://www.qimai.cn" + box.xpath(".//div[@class='info-content']//a/@href").extract()[0] # yield rankItem yield Request(infoUrl.replace("rank", "baseinfo"), self.parseInfo, meta={'rankItem': dict(rankItem).copy()}, dont_filter=True)
解析的infoUrl替換"rank"字符串爲"baseinfo"就能夠訪問app應用信息頁,用meta傳遞item到下一個解析方法中,用軟拷貝的方式,避免Item由於地址相同,內容覆蓋。
self.parseInfo爲指定此次請求的解析方法,
def parseInfo(self, response): print("基地址:" + response.url) if response.status != 200: return rankItem = response.meta['rankItem'] info = dict() base = response.xpath("//div[@id='app-container']") if base.extract(): # try: # 描述 try: info['desc'] = base.xpath( ".//div[@class='app-header']//div[@class='app-subtitle']/text()").extract()[0] except Exception as e: print("無描述") # 開發商 info['auther'] = base.xpath( ".//div[@class='app-header']//div[@class='auther']//div[@class='value']/text()").extract()[0] # 分類 info['classify'] = base.xpath( ".//div[@class='app-header']//div[@class='genre']//div[@class='value']/a/text()").extract()[0] # appid info['appid'] = base.xpath( ".//div[@class='app-header']//div[@class='appid']//div[@class='value']/a/text()").extract()[0] # appstore地址 info['appstorelink'] = base.xpath( ".//div[@class='app-header']//div[@class='appid']//div[@class='value']/a/@href").extract()[0] # 價格 info['price'] = base.xpath( ".//div[@class='app-header']//div[@class='price']//div[@class='value']/text()").extract()[0] # 最新版本 info['version'] = base.xpath( ".//div[@class='app-header']//div[@class='version']//div[@class='value']/text()").extract()[0] # 應用截圖 info['screenshot'] = base.xpath( ".//div[@class='router-wrapper']//div[@class='app-screenshot']//div[@class='screenshot-box']//img/@src").extract() # 應用描述 info['desc'] = base.xpath( ".//div[@class='router-wrapper']//div[@class='app-describe']//div[@class='description']").extract()[ 0] # 應用基本信息 info['baseinfo'] = [] for infoBase in base.xpath( ".//div[@class='router-wrapper']//div[@class='app-baseinfo']//ul[@class='baseinfo-list']/li"): # print(info['baseinfo']) try: info['baseinfo'].append(dict(type=infoBase.xpath(".//*[@class='type']/text()").extract()[0], info=infoBase.xpath(".//*[@class='info-txt']/text()").extract()[0])) except Exception as e: pass rankItem['info'] = info # 替換圖標 列表加載爲默認圖標 rankItem['src'] = \ response.xpath("//*[@id='app-side-bar']//div[@class='logo-wrap']/img/@src").extract()[ 0] yield rankItem
再次執行scrapy crawl RankSpider -o data1.json
,則可看見已經生成data2.json,可是生成的列表不是排行的列表,甚至是亂序的,緣由是由於咱們使用了url跟進返回,每一個頁面的請求返回的速度不同,須要排序的話就寫個小腳本按照index排個序。