經(jīng)過前面兩章簡書做基礎(chǔ),今天我們爬取相對(duì)難一點(diǎn)的項(xiàng)目-爬取用戶的博文相關(guān)信息-博文標(biāo)題、標(biāo)題鏈接、點(diǎn)擊數(shù)、評(píng)論數(shù)等;說到這里,有很多同學(xué)都做不住了:前面不是也是獲取過網(wǎng)頁的信息么,這些信息不是和前面一個(gè)樣;其實(shí),有這個(gè)想法是不錯(cuò)的,剛開始我也有這個(gè)想法,但是當(dāng)我真正去爬取的時(shí)候才發(fā)現(xiàn),“點(diǎn)擊數(shù)“ 和 “評(píng)論數(shù)” 是通過 js 動(dòng)態(tài)加載的,而博文標(biāo)題和博文鏈接是靜態(tài)的,因此這個(gè)實(shí)戰(zhàn)同時(shí)涉及到 靜態(tài) 和 動(dòng)態(tài) 網(wǎng)頁數(shù)據(jù)的獲取。
廢話不多說,接下來我們開搞,首先看下效果圖:

6.png
這里坑就坑在效果圖和顯示中是不一樣的,不過你怎么使用 xpath 表達(dá)式獲取“點(diǎn)擊數(shù)”和“評(píng)論數(shù)”就是只能得到默認(rèn)值,這讓人很是抓狂,大家可能有點(diǎn)不相信,那么我們就先用靜態(tài)的方式去獲取并輸出看看效果先
好,我們開始建立爬取博客項(xiàng)目:
scrapy startproject myblogproject(名字可自定義)
然后根據(jù)我們需要爬取的需求信息在 items.py 文件中定義爬取字段:
import scrapy
class MyblogprojectItem(scrapy.Item):
# define the fields for your item here like:
# 文章名
name = scrapy.Field()
# 文章地址
url = scrapy.Field()
# 文章閱讀數(shù)
hits = scrapy.Field()
# 文章評(píng)論數(shù)
comment = scrapy.Field()
實(shí)體字段定義好之后我們創(chuàng)建一個(gè)基于 basic 版本的爬蟲文件:
scrapy genspider -t basic blogspider(爬蟲文件名可自定義) hexun.com
聰明的同學(xué)在編輯爬蟲文件之前會(huì)想到要去觀察下?lián)Q頁 url 的變化:

7.png
從圖中紅色方括號(hào)可以清晰的看到,第一個(gè)是用戶ID,第二個(gè)是頁碼
接下來開始編輯爬蟲文件:
# -*- coding: utf-8 -*-
import scrapy
from myblogproject.items import MyblogprojectItem
import re
import requests
class BlogspiderSpider(scrapy.Spider):
name = 'blogspider'
allowed_domains = ['hexun.com']
offset = 1 #偏移量
user_id = "toureiffel" #用戶id,要爬取不同用戶的中的文章數(shù)據(jù)換一下用戶ID即可
# user_id = "xqw55555"
# user_id = "tianyahaijiaoke"
# user_id = "tjhyb2011"
my_url = 'http://'+ user_id +'.blog.hexun.com/p1/default.html'
start_urls = [my_url]
def parse(self, response):
item = MyblogprojectItem()
node_list = response.xpath('//div[@class="Article"]')
for node in node_list:
item['name'] = node.xpath('./div/span/a/text()').extract_first()
item['url'] = node.xpath('./div/span/a/@href').extract_first()
#由于點(diǎn)擊數(shù)和評(píng)論數(shù)是動(dòng)態(tài)獲取的,因此不能直接在源碼中直接抓取
item["hits"] = node.xpath('./div[2]/div[2]/span/text()').extract_first() # 0
item["comment"] = node.xpath('./div[2]/div[2]/a[2]/span/text()').extract_first() # 0
print(item['name'], item['url'], item["hits"], item["comment"])#輸出測試
yield item
接下來一步就是在設(shè)置文件中做相應(yīng)的反屏蔽設(shè)置:
#以下幾個(gè)設(shè)置可以基本達(dá)到被反屏蔽的效果(當(dāng)然這只是簡單的幾種方式而已)
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
ROBOTSTXT_OBEY = False
COOKIES_ENABLED = False
我們接下來先測試一下運(yùn)行效果:

8.png
為何會(huì)出現(xiàn)如此奇葩的事情?觀察了好久,終究還是發(fā)現(xiàn)了那兩個(gè)玩意是動(dòng)態(tài)加載的,乖乖了。。。接下來我們一起去看下它到底是張啥樣的:

9.png
知道了這些數(shù)據(jù)的格式并且知道了這個(gè)鏈接,我們把鏈接復(fù)制一份在瀏覽器中打開發(fā)現(xiàn)其數(shù)據(jù)和剛才我們?cè)陂_發(fā)者模式看到的一模一樣,那么我們?nèi)绾问褂么a獲取呢???好,我們打開網(wǎng)頁的源代碼,然后將動(dòng)態(tài)鏈接復(fù)制過來查找一下,發(fā)現(xiàn)這個(gè)鏈接其實(shí)就在我們的源代碼中有:

10.png
好了,完事具備,就差我們?nèi)绾稳バ薷呐老x文件了:
# -*- coding: utf-8 -*-
import scrapy
from myblogproject.items import MyblogprojectItem
import re
import requests
class BlogspiderSpider(scrapy.Spider):
name = 'blogspider'
allowed_domains = ['hexun.com']
offset = 1 #偏移量
user_id = "toureiffel" #用戶id,要爬取不同用戶的中的文章數(shù)據(jù)換一下用戶ID即可
# user_id = "xqw55555"
# user_id = "tianyahaijiaoke"
# user_id = "tjhyb2011"
my_url = 'http://'+ user_id +'.blog.hexun.com/p1/default.html'
start_urls = [my_url]
def parse(self, response):
item = MyblogprojectItem()
#編寫獲取點(diǎn)擊數(shù)和評(píng)論數(shù) 鏈接 的正則表達(dá)式
reg = r'<script type="text/javascript" src="(http://click.tool.hexun.com/.*?)">'
myurl = re.compile(reg, re.S).findall(str(response.body)) #注:這里必先把字節(jié)轉(zhuǎn)換成字符串才能使用正則
#print("提取的點(diǎn)贊和評(píng)論連接為-> "+ myurl[0]) #輸出測試
headers = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36" }
html = requests.get(myurl[0], headers=headers)#請(qǐng)求
html.encoding = "utf-8" #編碼
data = html.text #獲取源碼文本
#提取點(diǎn)擊數(shù)的正則表達(dá)式
hits_reg = r"click\d*','(\d*)'"
hits_list = re.compile(hits_reg, re.S).findall(data)#獲取點(diǎn)擊數(shù)的列表
#提取評(píng)論數(shù)的正則表達(dá)式
common_reg = r"comment\d*','(\d*)'"
common_list = re.compile(common_reg, re.S).findall(data)#獲取評(píng)論數(shù)的列表
#賦值給變量
item['hits'] = hits_list
item['comment'] = common_list
#使用 xpath 表達(dá)式獲取
item['name'] = response.xpath('//div[@class="Article"]/div/span/a/text()').extract()
item['url'] = response.xpath('//div[@class="Article"]/div/span/a/@href').extract()
for i in range(0, len(item["name"])):
#保存為csv文件
blog_list = [item["name"][i], item["url"][i], item["hits"][i], item["comment"][i]]
print("測試數(shù)據(jù)-> "+ str(blog_list))#輸出測試
yield item
費(fèi)了那么大的力氣,接下來運(yùn)行測試一下:

11.png
這下終于出來了,哈哈哈,咱們一鼓做氣,把分頁頁搞定了,分頁爬取有兩種方式:一種是判斷“下一頁”按鈕,另一種就是拼接 url 地址(這兩種我們前面一個(gè)文章都詳細(xì)的分析過了,在這里我們使用拼接url方式,當(dāng)然如果使用拼接并且不用我們每次都去數(shù)論文的總數(shù)那才叫絕),修改代碼之前我們先看下源碼的截圖:

12.png
看到了吧,哈哈哈,總頁數(shù)是在源碼中可以獲取的,這樣我們就可以開始分頁的去爬取了,哈哈哈,在原來的爬蟲文件爬取一頁代碼的下面添加如下代碼即可:
#獲取總頁數(shù)
page_reg = r'blog.hexun.com/p(.*?)/'
sum_page_list = re.compile(page_reg, re.S).findall(str(response.body))
#如果用戶的文章只有一頁的話就無法得到總頁數(shù),因此我們必須寫個(gè)兼容這種情況的方法
sum_page = 0 #存儲(chǔ)總頁數(shù)的變量
if(len(sum_page_list) >= 2):
sum_page = sum_page_list[-2]
else:
sum_page = 1 #如果只有一頁則賦值給1即可
if self.offset < int(sum_page): #偏移量必須要小于總頁數(shù)
self.offset += 1 #頁數(shù)記得自增
print("-> 正在爬取第 %s 頁..."% str(self.offset))#輸出測試
new_url = 'http://'+ self.user_id +'.blog.hexun.com/p'+ str(self.offset) +'/default.html'#拼接分頁的 url
yield scrapy.Request(new_url, callback = self.parse, headers=headers)#回調(diào)方法處理的是請(qǐng)求之后的數(shù)據(jù)
到這里我們?cè)龠\(yùn)行測試時(shí)發(fā)現(xiàn)可以正常全部數(shù)據(jù)數(shù)據(jù)(在這里不再截圖展示),然后我們要把我們剛獲取得的數(shù)據(jù)保存到本地文件中,那么我們就應(yīng)該在實(shí)體管道文件 pipelines.py 中處理數(shù)據(jù)了:
import csv
class MyblogprojectPipeline(object):
def __init__(self):
self.f = open("blogs.csv", "w")
self.writer = csv.writer(self.f)
self.writer.writerow(['博文標(biāo)題', '博文鏈接', '閱讀數(shù)', '評(píng)論數(shù)'])
def process_item(self, item, spider):
#循環(huán)寫入本地文件
for i in range(0, len(item["name"])):
#保存為csv文件
blog_list = [item["name"][i], item["url"][i], item["hits"][i], item["comment"][i]]
print(blog_list)#輸出測試
self.writer.writerow(blog_list)
return item
def close_spider(self, spider):
self.f.close()
這里就要注意了,要使管道文件有效,我們就必須在配置文件 settings.py 中將它開啟(這里值得一提的是管道配置文件的位置最好不要改變):
#啟動(dòng)管道
ITEM_PIPELINES = {
'myblogproject.pipelines.MyblogprojectPipeline': 300,
}
好了,我們?cè)贉y試一次(這里為了清晰的看數(shù)據(jù)數(shù)據(jù),我把爬蟲文件的輸出測試語句給注釋掉了),然后開發(fā)本地文件:

13.png