Python網路爬蟲(九):爬取頂點小說網站全部小說,並存入MongoDB
阿新 • • 發佈:2019-01-10
前言:本篇部落格將爬取頂點小說網站全部小說、涉及到的問題有:Scrapy架構、斷點續傳問題、Mongodb資料庫相關操作。
背景:
Python版本:Anaconda3
執行平臺:Windows
IDE:PyCharm
資料庫:MongoDB
瀏覽器工具: Chrome瀏覽器
前面的部落格中已經對Scrapy作了相當多的介紹所以這裡不再對Scrapy技術作過多的講解。
一、爬蟲準備工作:
此次我們爬取的是免費小說網站:頂點小說
我們要想把它全部的小說爬取下來,是不是得有全部
小說的連結?
我們看到頂點小說網站上有一個總排行榜。
點選進入後我們看到,這裡有網站上所有的小說,一共有1144頁,每頁大約20本小說,算下來一共大約有兩萬兩千多本,是一個龐大的資料量,並且小說的數量還在不斷的增長中。
好!我們遇到了第一個問題,如何獲取總排行榜中的頁數呢?也就是現在的“1144”。
1、獲取排行榜頁面數:
最好的方法就是用Xpath。
我們先用F12審查元素,看到“1144”放在了“id”屬性為“pagestats”的em節點中。
我們再用Scrapy Shell分析一下網頁。
注意:Scrapy Shell是一個非常好的工具,我們在編寫爬蟲過程中,可以用它不斷的測試我們編寫的Xpath語句,非常方便。
輸入命令:
scrapy shell "http://www.23us.so/top/allvisit_2.html"
然後就進入了scrapy shell
因為頁數放在“id”屬性為“pagestats”的em節點中,所以我們可以在shell中輸入如下指令獲取。
response.xpath('//*[@id="pagestats"]/text()').extract_first()
我們可以看到,Xpath一如既往的簡單高效,頁面數已經被擷取下來了。
2、獲取小說主頁連結、小說名稱:
接下來,我們遇到新的問題,如何獲得每個頁面上的小說的連結呢?我們再來看頁面的HTML程式碼。
小說的連結放在了“a”節點裡,而且這樣的a節點區別其他的“a”節點的是,沒有“title”屬性。
所以我們用shell測試一下,輸入命令:
response.xpath('//td/a[not(@title)]/@href').extract()
我們看到,小說的連結地址我們抓到了。
同樣還有小說名,
response.xpath('//td/a[not(@title)]/text()').extract()
我們可以看到頁面上的小說名稱我們也已經抓取到了。
3、獲取小說詳細資訊:
我們點開頁面上的其中一個小說連結:
這裡有小說的一些相關資訊和小說章節目錄的地址。
我們想要的資料首先是小說全部章節目錄的地址,然後是小說類別、小說作者、小說狀態、小說最後更新時間。
我們先看小說全部章節目錄的地址。用F12,我們看到:
小說全部章節地址放在了“class”屬性為“btnlinks”的“p”節點的第一個“a”節點中。
我們還是用scrapy shell測試一下我們寫的xpath語句。
鍵入命令,進入shell介面
scrapy shell "http://www.23us.so/xiaoshuo/13007.html"
在shell中鍵入命令:
response.xpath('//p[@class="btnlinks"]/a[1]/@href').extract_first()
小說的章節目錄頁面我們已經擷取下來了。
類似的還有小說類別、小說作者、小說狀態、小說最後更新時間,命令分別是:
#小說類別
response.xpath('//table/tr[1]/td[1]/a/text()').extract_first()
#小說作者
response.xpath('//table/tr[1]/td[2]/text()').extract_first()
#小說狀態
response.xpath('//table/tr[1]/td[3]/text()').extract_first()
#小說最後更新時間
response.xpath('//table/tr[2]/td[3]/text()').extract_first()
4、獲取小說全部章節:
我們點開“最新章節”,來到小說全部章節頁面。
我們如何獲得這些連結呢?答案還是Xpath。
用F12看到,各章節地址和章節名稱放在了一個“table”中:
退出上次的scrapy shell ,分析 全部章節頁面。
scrapy shell "http://www.23us.so/files/article/html/13/13007/index.html"
在shell中鍵入Xpath語句:
response.xpath('//table/tr/td/a/@href').extract()
同樣還有各章節名稱
response.xpath('//table/tr/td/a/text()').extract()
5、爬取小說章節內容:
好了,小說各個章節地址我們擷取下來了,接下來就是小說各個章節的內容。
我們用F12看到,章節內容放在了“id”屬性為“contents”的“dd”節點中。
這裡我們再用Xpath看一下,鍵入Xpath語句:
Response.xpath('//dd[@id="contents"]').extract()
我們看到,小說內容已經讓我們擷取到了!
二、編寫爬蟲:
整個流程上面已經介紹過了,還有一個非常重要的問題:
斷點續傳問題
我們知道,爬蟲不可能一次將全部網站爬取下來,網站的資料量相當龐大,在短時間內不可能完成爬蟲工作,在下一次啟動爬蟲時難道再將已經做過的工作再做一次?當然不行,這樣的爬蟲太不友好。那麼我們如何來解決斷點續傳問題呢?
我這裡的方法是,將已經爬取過的小說每一章的連結存入Mongodb資料庫的一個集合中。在爬蟲工作時首先檢測,要爬取的章節連結是否在這個集合中:
如果在,說明這個章節已經爬取過,不需要再次爬取,跳過;
如果不在,說明這個章節沒有爬取過,則爬取這個章節。爬取完成後,將這個章節連結存入集合中;
如此,我們就完美實現了斷點續傳問題,十分好用。
接下來貼出整個專案程式碼:
註釋我寫的相當詳細,熟悉一下就可以看懂。
items.py
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class DingdianxiaoshuoItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
#小說名字
novel_name=scrapy.Field()
#小說類別
novel_family=scrapy.Field()
#小說主頁地址
novel_url=scrapy.Field()
#小說作者
novel_author=scrapy.Field()
#小說狀態
novel_status=scrapy.Field()
#小說字數
novel_number=scrapy.Field()
#小說所有章節頁面
novel_all_section_url= scrapy.Field()
#小說最後更新時間
novel_updatetime=scrapy.Field()
#存放小說的章節地址,程式中存放的是一個列表
novel_section_urls=scrapy.Field()
#存放小說的章節地址和小說章節名稱的對應關係,程式中儲存的是一個字典
section_url_And_section_name=scrapy.Field()
dingdian.py
# -*- coding: utf-8 -*-
import scrapy
from scrapy import Selector
from dingdianxiaoshuo.items import DingdianxiaoshuoItem
class dingdian(scrapy.Spider):
name="dingdian"
allowed_domains=["23us.so"]
start_urls = ['http://www.23us.so/top/allvisit_1.html']
server_link='http://www.23us.so/top/allvisit_'
link_last='.html'
#從start_requests傳送請求
def start_requests(self):
yield scrapy.Request(url = self.start_urls[0], callback = self.parse1)
#獲取總排行榜每個頁面的連結
def parse1(self, response):
items=[]
res = Selector(response)
#獲取總排行榜小說頁碼數
max_num=res.xpath('//*[@id="pagestats"]/text()').extract_first()
max_num=max_num.split('/')[1]
print("總排行榜最大頁面數為:"+max_num)
#for i in max_num+1:
for i in range(0,int(max_num)):
#構造總排行榜中每個頁面的連結
page_url=self.server_link+str(i)+self.link_last
yield scrapy.Request(url=page_url,meta={'items':items},callback=self.parse2)
#訪問總排行榜的每個頁面
def parse2(self,response):
print(response.url)
items=response.meta['items']
res=Selector(response)
#獲得頁面上所有小說主頁連結地址
novel_urls=res.xpath('//td/a[not(@title)]/@href').extract()
#獲得頁面上所有小說的名稱
novel_names=res.xpath('//td/a[not(@title)]/text()').extract()
page_novel_number=len(novel_urls)
for index in range(page_novel_number):
item=DingdianxiaoshuoItem()
item['novel_name']=novel_names[index]
item['novel_url'] =novel_urls[index]
items.append(item)
for item in items:
#訪問每個小說主頁,傳遞novel_name
yield scrapy.Request(url=item['novel_url'],meta = {'item':item},callback = self.parse3)
#訪問小說主頁,繼續完善item
def parse3(self, response):
#接收傳遞的item
item=response.meta['item']
#寫入小說類別
item['novel_family']=response.xpath('//table/tr[1]/td[1]/a/text()').extract_first()
#寫入小說作者
item['novel_author']=response.xpath('//table/tr[1]/td[2]/text()').extract_first()
#寫入小說狀態
item['novel_status']=response.xpath('//table/tr[1]/td[3]/text()').extract_first()
#寫入小說最後更新時間
item['novel_updatetime']=response.xpath('//table/tr[2]/td[3]/text()').extract_first()
#寫入小說全部章節頁面
item['novel_all_section_url']=response.xpath('//p[@class="btnlinks"]/a[1]/@href').extract_first()
url=response.xpath('//p[@class="btnlinks"]/a[@class="read"]/@href').extract_first()
#訪問顯示有全部章節地址的頁面
print("即將訪問"+item['novel_name']+"全部章節地址")
#yield item
yield scrapy.Request(url=url,meta={'item':item},callback=self.parse4)
#將小說所有章節的地址和名稱構造列表存入item
def parse4(self, response):
#print("這是parse4")
#接收傳遞的item
item=response.meta['item']
#這裡是一個列表,存放小說所有章節地址
section_urls=response.xpath('//table/tr/td/a/@href').extract()
#這裡是一個列表,存放小說所有章節名稱
section_names=response.xpath('//table/tr/td/a/text()').extract()
item["novel_section_urls"]=section_urls
#計數器
index=0
#建立雜湊表,儲存章節地址和章節名稱的對應關係
section_url_And_section_name=dict(zip(section_urls,section_names))
#將對應關係,寫入item
item["section_url_And_section_name"]=section_url_And_section_name
yield item
settings.py
# -*- coding: utf-8 -*-
# Scrapy settings for dingdianxiaoshuo project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://doc.scrapy.org/en/latest/topics/settings.html
# https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'dingdianxiaoshuo'
SPIDER_MODULES = ['dingdianxiaoshuo.spiders']
NEWSPIDER_MODULE = 'dingdianxiaoshuo.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'dingdianxiaoshuo (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 0.25
#CLOSESPIDER_TIMEOUT = 60 # 後結束爬蟲
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# Enable or disable spider middlewares
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'dingdianxiaoshuo.middlewares.DingdianxiaoshuoSpiderMiddleware': 543,
#}
# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'dingdianxiaoshuo.middlewares.DingdianxiaoshuoDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'dingdianxiaoshuo.pipelines.DingdianxiaoshuoPipeline': 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
pipeline.py
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
#因為爬取整個網站時間較長,這裡為了實現斷點續傳,我們把每個小說下載完成的
#章節地址存入資料庫一個單獨的集合裡,記錄已完成抓取的小說章節
from pymongo import MongoClient
from urllib import request
from bs4 import BeautifulSoup
#在pipeline中我們將實現下載每個小說,存入MongoDB資料庫
class DingdianxiaoshuoPipeline(object):
def process_item(self, item, spider):
#print("馬衍碩")
#如果獲取章節連結進行如下操作
if "novel_section_urls" in item:
# 獲取Mongodb連結
client = MongoClient("mongodb://127.0.0.1:27017")
#連線資料庫
db =client.dingdian
#獲取小說名稱
novel_name=item['novel_name']
#根據小說名字,使用集合,沒有則建立
novel=db[novel_name]
#使用記錄已抓取網頁的集合,沒有則建立
section_url_downloaded_collection=db.section_url_collection
index=0
print("正在下載:"+item["novel_name"])
#根據小說每個章節的地址,下載小說各個章節
for section_url in item['novel_section_urls']:
#根據對應關係,找出章節名稱
section_name=item["section_url_And_section_name"][section_url]
#如果將要下載的小說章節沒有在section_url_collection集合中,也就是從未下載過,執行下載
#否則跳過
if not section_url_downloaded_collection.find_one({"url":section_url}):
#使用urllib庫獲取網頁HTML
response = request.Request(url=section_url)
download_response = request.urlopen(response)
download_html = download_response.read().decode('utf-8')
#利用BeautifulSoup對HTML進行處理,擷取小說內容
soup_texts = BeautifulSoup(download_html, 'lxml')
content=soup_texts.find("dd",attrs={"id":"contents"}).getText()
#向Mongodb資料庫插入下載完的小說章節內容
novel.insert({"novel_name": item['novel_name'], "novel_family": item['novel_family'],
"novel_author":item['novel_author'], "novel_status":item['novel_status'],
"section_name":section_name,
"content": content})
index+=1
#下載完成,則將章節地址存入section_url_downloaded_collection集合
section_url_downloaded_collection.insert({"url":section_url})
print("下載完成:"+item['novel_name'])
return item
三、啟動專案,檢視執行結果:
程式編寫完成後,我們進入專案所在目錄,鍵入命令啟動專案:
scrapy crawl dingdian
啟動專案後,我們通過Mongodb視覺化工具–RoBo看到,我們成功爬取了小說網站,接下來的問題交給時間。