需求
爬取古詩文網(wǎng)站中的 詩詞的 標題、作者、朝代、內(nèi)容、以及翻頁并保存

工具和環(huán)境
語言:python 3.6
IDE: Pycharm
瀏覽器:Chrome
爬蟲框架:Scrapy 2.5.0
爬蟲實現(xiàn)
第一步:頁面分析
第一頁:https://www.gushiwen.org/default_1.aspx
第二頁:https://www.gushiwen.cn/default_2.aspx 點擊"下一頁"按鈕
第三頁:https://www.gushiwen.cn/default.aspx?page=3 在數(shù)字框里面輸入3(無視了)
第三頁:https://www.gushiwen.cn/default_3.aspx 點擊"下一頁"按鈕

在第二頁的基礎(chǔ)上點擊“下一頁”按鈕得到的URL地址是:https://www.gushiwen.cn/default_3.aspx
這個和上面分析的第一頁和第二頁的URL地址相似,所以我們忽視在“數(shù)字框里面輸入數(shù)字”得到的URL地址。
觀察頁面URL發(fā)現(xiàn)有兩個域名:“gushiwen.org”,“gushiwen.cn”
這兩個域名都可以使用, .org 和 .cn 是一樣的效果。
第二步:創(chuàng)建scrapy項目
在Pycharm終端里面輸入如下命令創(chuàng)建scrapy項目
1.到項目所在的目錄下創(chuàng)建scrapy項目:scrapy startproject gsw
2.進入到gsw目錄下創(chuàng)建爬蟲:scrapy genspider gs gushiwen.org
第三步:代碼邏輯實現(xiàn)
代碼實現(xiàn)主要分如下的幾步:
1.分析第一個URL頁面HTML標簽結(jié)構(gòu)并提取數(shù)據(jù)
2.用item封裝提取到的數(shù)據(jù)并傳遞給管道
3.管道文件里實現(xiàn)數(shù)據(jù)的保存
4.實現(xiàn)其他頁面數(shù)據(jù)的爬取---翻頁
分析第一個URL頁面HTML標簽結(jié)構(gòu)并提取數(shù)據(jù)
選中某一首詩的標題或詩文,點擊鼠標右鍵菜單中的“檢查”按鈕

可以查看到網(wǎng)頁對應的前端HTML標簽,并自動給我們定位到了這個標題HTML標簽的位置,如標題<b>螽斯</b>

通過該標題標簽一步步推導,發(fā)現(xiàn)”每首詩”的布局都是一個<div class=”sons”>…</div>的標簽內(nèi),然后頁面中的所有的“詩”都在共同的標簽<div class=”left”>…</div>標簽下

初步分析后,我們嘗試獲取“詩的標題”,修改代碼:
import scrapy
from gsw.items import GswItem
class GsSpider(scrapy.Spider):
name = 'gs'
allowed_domains = ['gushiwen.org', 'gushiwen.cn'] # 修改,增加一個相似域名
start_urls = ['https://www.gushiwen.cn/default_1.aspx'] # 修改為起始第一個URL地址,爬蟲程序就是從這一頁開始數(shù)據(jù)的
# 該爬蟲先從上面起始的第一個URL地址開始發(fā)出請求,并得到請求響應的數(shù)據(jù),得到響應數(shù)據(jù)后,數(shù)據(jù)的解析就用如下的parse函數(shù)進行解析
def parse(self, response):
gsw_divs = response.xpath('//div[@class="left"]/div[@class="sons"]') # xpath返回的是列表(每個div sons標簽,也就是每首詩)
for gsw_div in gsw_divs: # 對每首詩進行遍歷
title = gsw_div.xpath('.//b/text()').extract_first() # xpath返回的是列表(取列表第一個也就是詩的標題)
print(title)
打印輸出結(jié)果: 結(jié)果中含有None說明對應的div中沒有<b>標簽,說明頁面中該部分顯示的不是一首詩

接著獲取 詩的 作者 和 朝代

import scrapy
from gsw.items import GswItem
class GsSpider(scrapy.Spider):
name = 'gs'
allowed_domains = ['gushiwen.org', 'gushiwen.cn'] # 修改,增加一個相似域名
start_urls = ['https://www.gushiwen.cn/default_1.aspx'] # 修改為起始第一個URL地址,爬蟲程序就是從這一頁開始數(shù)據(jù)的
# 該爬蟲先從上面起始的第一個URL地址開始發(fā)出請求,并得到請求響應的數(shù)據(jù),得到響應數(shù)據(jù)后,數(shù)據(jù)的解析就用如下的parse函數(shù)進行解析
def parse(self, response):
gsw_divs = response.xpath('//div[@class="left"]/div[@class="sons"]') # xpath返回的是列表(每個div sons標簽,也就是每首詩)
for gsw_div in gsw_divs: # 對每首詩進行遍歷
title = gsw_div.xpath('.//b/text()').extract_first() # xpath返回的是列表(取列表第一個也就是詩的標題)
if title:
source = gsw_div.xpath('.//p[@class="source"]/a/text()').extract() # 獲取 作者 和 朝代 的列表
author = source[0] # 獲取 作者
dynasty = source[1] # 獲取 朝代
接著獲取 詩的內(nèi)容

import scrapy
from gsw.items import GswItem
class GsSpider(scrapy.Spider):
name = 'gs'
allowed_domains = ['gushiwen.org', 'gushiwen.cn'] # 修改,增加一個相似域名
start_urls = ['https://www.gushiwen.cn/default_1.aspx'] # 修改為起始第一個URL地址,爬蟲程序就是從這一頁開始數(shù)據(jù)的
# 該爬蟲先從上面起始的第一個URL地址開始發(fā)出請求,并得到請求響應的數(shù)據(jù),得到響應數(shù)據(jù)后,數(shù)據(jù)的解析就用如下的parse函數(shù)進行解析
def parse(self, response):
gsw_divs = response.xpath('//div[@class="left"]/div[@class="sons"]') # xpath返回的是列表(每個div sons標簽,也就是每首詩)
for gsw_div in gsw_divs: # 對每首詩進行遍歷
title = gsw_div.xpath('.//b/text()').extract_first() # xpath返回的是列表(取列表第一個也就是詩的標題)
if title:
source = gsw_div.xpath('.//p[@class="source"]/a/text()').extract() # 獲取 作者 和 朝代 的列表
author = source[0] # 獲取 作者
dynasty = source[1] # 獲取 朝代
content_lst = gsw_div.xpath('.//div[@class="contson"]//text()').extract() # 獲取 詩的文本內(nèi)容
content = ''.join(content_lst).strip() # 列表中的每個元素用空串拼接,去除列表并用strip()方法移除字符串頭尾指定的字符
至此 詩的 標題、作者、朝代、內(nèi)容 這些數(shù)據(jù)都拿到了。。。
接下來實現(xiàn)翻頁并保存數(shù)據(jù)
用item封裝提取到的數(shù)據(jù)并傳遞給管道
來看下scrapy項目的目錄結(jié)構(gòu)

gs.py:是我們實現(xiàn)的爬蟲文件
items.py:是實現(xiàn)數(shù)據(jù)的封裝,提前定義好要爬取的數(shù)據(jù)內(nèi)容
pipelines.py:是實現(xiàn)數(shù)據(jù)的保存
settings.py:是scrapy項目相關(guān)的設(shè)置
middlewares.py:以后用到再講
我們在items.py里定義好我們要爬取的內(nèi)容
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class GswItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
author = scrapy.Field()
dynasty = scrapy.Field()
content = scrapy.Field()
pass
然后在爬蟲文件里面導入這個items.py里面定義的數(shù)據(jù)類GswItem
導入方式有兩種:
1.從項目最開始目錄開始一級一級導入
from projectRootDir.demo.gsw.gsw.items import GswItem
2.將創(chuàng)建scrapy項目的目錄作為根目錄 Mark Directory as -> Source Root
from gsw.items import GswItem
然后在爬蟲文件里面創(chuàng)建items對象并把數(shù)據(jù)賦值給items對象,賦值的方式見源碼,最后把數(shù)據(jù)傳遞給管道文件pipelines.py
import scrapy
from gsw.items import GswItem
class GsSpider(scrapy.Spider):
name = 'gs'
allowed_domains = ['gushiwen.org', 'gushiwen.cn'] # 修改,增加一個相似域名
start_urls = ['https://www.gushiwen.cn/default_1.aspx'] # 修改為起始第一個URL地址,爬蟲程序就是從這一頁開始數(shù)據(jù)的
# 該爬蟲先從上面起始的第一個URL地址開始發(fā)出請求,并得到請求響應的數(shù)據(jù),得到響應數(shù)據(jù)后,數(shù)據(jù)的解析就用如下的parse函數(shù)進行解析
def parse(self, response):
gsw_divs = response.xpath('//div[@class="left"]/div[@class="sons"]') # xpath返回的是列表(每個div sons標簽,也就是每首詩)
for gsw_div in gsw_divs: # 對每首詩進行遍歷
title = gsw_div.xpath('.//b/text()').extract_first() # xpath返回的是列表(取列表第一個也就是詩的標題)
if title:
source = gsw_div.xpath('.//p[@class="source"]/a/text()').extract() # 獲取 作者 和 朝代 的列表
author = source[0] # 獲取 作者
dynasty = source[1] # 獲取 朝代
content_lst = gsw_div.xpath('.//div[@class="contson"]//text()').extract() # 獲取 詩的文本內(nèi)容
content = ''.join(content_lst).strip() # 列表中的每個元素用空串拼接,去除列表并用strip()方法移除字符串頭尾指定的字符
# 方式一
# item = GswItem() # 創(chuàng)建items對象并賦值
# item['title'] = title
# item['author'] = author
# item['dynasty'] = dynasty
# item['content'] = content
# 方式二
item = GswItem(title=title, author=author, dynasty=dynasty, content=content) # 創(chuàng)建items對象并賦值
yield item # 將item數(shù)據(jù)傳給管道 pipelines
注意:item與管道之間數(shù)據(jù)傳遞時,需要在settings.py里面把item pipelines相關(guān)的設(shè)置項打開
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'gsw.pipelines.GswPipeline': 300,
}
管道文件里實現(xiàn)數(shù)據(jù)的保存
接下來我們到pipelines.py文件中處理數(shù)據(jù)的保存,簡單數(shù)據(jù)保存如下
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
import json
class GswPipeline:
def open_spider(self, spider):
self.fp = open('gsw.txt', 'w', encoding='utf-8')
# item 是從爬蟲文件傳遞過來的數(shù)據(jù),是GswItem類的一個實例化對象
def process_item(self, item, spider):
item_json = json.dumps(dict(item), ensure_ascii=False)
self.fp.write(item_json + '\n')
return item
def close_spider(self, spider):
self.fp.close()
實現(xiàn)其他頁面數(shù)據(jù)的爬取---翻頁
實現(xiàn)自動翻頁一般有兩種方法:
1.在頁面中找到下一頁的地址;
2.自己根據(jù)URL的變化規(guī)律構(gòu)造所有頁面地址;
一般情況下我們使用第一種方法,第二種方法適用于頁面的下一頁地址為JS加載的情況。我們現(xiàn)在只說第一種方法。
首先到首頁的最底部找到下一頁的按鈕,然后點擊右鍵檢查,會自動定位到“下一頁”按鈕元素位置處。

通過第一頁請求響應到的response數(shù)據(jù)解析到下一頁的鏈接:
next_href = response.xpath('//a[@id="amore"]/@href').get().get())
然后重新調(diào)用scrapy.Request(next_url)函數(shù)發(fā)出下一頁數(shù)據(jù)的請求,重新走一遍和第一頁數(shù)據(jù)請求一樣的完整流程(爬蟲請求 爬蟲引擎 調(diào)度器 爬蟲引擎 下載器 爬蟲引擎 爬蟲響應 管道 …)
注意:scrapy.Request第二個參數(shù)callback是響應數(shù)據(jù)解析處理函數(shù),下一頁的數(shù)據(jù)解析函數(shù)和第一頁是相同的(parse)所以可以省略不寫,如果不同就需要另外編寫比如每首詩的詳情頁,后面補充。。。
翻頁的邏輯代碼實現(xiàn)如下:
# 翻頁邏輯處理
next_href = response.xpath('//a[@id="amore"]/@href').get()
if next_href:
# next_url = 'https://www.gushiwen.cn' + next_href[0] # URL第一種補全方式
next_url = response.urljoin(next_href) # URL第二種補全方式(urljoin可以進行URL地址的補全)
yield scrapy.Request(next_url) # 爬蟲程序重新通過 爬蟲引擎 調(diào)度器 下載器 請求下一頁的數(shù)據(jù)
# yield scrapy.Request(url=next_url, callback=self.parse) # 或者這種方式請求
完整的爬蟲程序如下:
import scrapy
from gsw.items import GswItem
class GsSpider(scrapy.Spider):
name = 'gs'
allowed_domains = ['gushiwen.org', 'gushiwen.cn'] # 修改,增加一個相似域名
start_urls = ['https://www.gushiwen.cn/default_1.aspx'] # 修改為起始第一個URL地址,爬蟲程序就是從這一頁開始數(shù)據(jù)的
# 該爬蟲先從上面起始的第一個URL地址開始發(fā)出請求,并得到請求響應的數(shù)據(jù),得到響應數(shù)據(jù)后,數(shù)據(jù)的解析就用如下的parse函數(shù)進行解析
def parse(self, response):
gsw_divs = response.xpath('//div[@class="left"]/div[@class="sons"]') # xpath返回的是列表(每個div sons標簽,也就是每首詩)
for gsw_div in gsw_divs: # 對每首詩進行遍歷
title = gsw_div.xpath('.//b/text()').extract_first() # xpath返回的是列表(取列表第一個也就是詩的標題)
if title:
source = gsw_div.xpath('.//p[@class="source"]/a/text()').extract() # 獲取 作者 和 朝代 的列表
author = source[0] # 獲取 作者
dynasty = source[1] # 獲取 朝代
content_lst = gsw_div.xpath('.//div[@class="contson"]//text()').extract() # 獲取 詩的文本內(nèi)容
content = ''.join(content_lst).strip() # 列表中的每個元素用空串拼接,去除列表并用strip()方法移除字符串頭尾指定的字符
# 方式一
# item = GswItem() # 創(chuàng)建items對象并賦值
# item['title'] = title
# item['author'] = author
# item['dynasty'] = dynasty
# item['content'] = content
# 方式二
item = GswItem(title=title, author=author, dynasty=dynasty, content=content) # 創(chuàng)建items對象并賦值
yield item # 將item數(shù)據(jù)傳給管道 pipelines
# 翻頁邏輯處理
next_href = response.xpath('//a[@id="amore"]/@href').get()
if next_href:
# next_url = 'https://www.gushiwen.cn' + next_href[0] # URL第一種補全方式
next_url = response.urljoin(next_href) # URL第二種補全方式(urljoin可以進行URL地址的補全)
yield scrapy.Request(next_url) # 爬蟲程序重新通過 爬蟲引擎 調(diào)度器 下載器 請求下一頁的數(shù)據(jù)
# yield scrapy.Request(url=next_url, callback=self.parse) # 或者這種方式請求
程序運行結(jié)果:
打開在管道文件中創(chuàng)建的“gsw.txt”發(fā)現(xiàn)有50首詩,總共有5頁,每頁10首,數(shù)據(jù)爬取正確。

結(jié)尾和補充
1.當爬取的范圍不一樣的時候(域名不一樣的時候)
allowed_domains = ['gushiwen.org', 'gushiwen.cn'] 可以加多個
2.注意處理列表為空的數(shù)據(jù)(通過非空判斷,通過try語句等)
3.翻頁處理(1.可以找頁數(shù)的規(guī)律;2.直接找下一頁的URL,然后yield scrappy.Request(next_url))
4.遇到URL不全的時候,建議補全URL地址(1.拼串;2.urljoin() 這個是scrapy提供的)
5.在settings.py里面除了打開item pipelines配置項外,也需要修改默認請求頭,增加User-Agent請求頭
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
配置打印級別:LOG_LEVEL = 'WARNING'
Obey rules改成False:ROBOTSTXT_OBEY = False
BOT_NAME = 'gsw'
SPIDER_MODULES = ['gsw.spiders']
NEWSPIDER_MODULE = 'gsw.spiders'
LOG_LEVEL = 'WARNING'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'gsw (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False