寫在前面的話
喜歡看小說,平時都是通過電腦或者手機看小說,手機聽小說(智能語音),或者喜馬拉雅搜索小說聽(好多喜歡的都收費o(╥﹏╥)o,然后網上好多免費資源卻不能聽),想在電腦上聽小說,目前Microsoft Edge可以閱讀網頁文本很贊,不能自動翻譯很煩(# ̄~ ̄#),而且智能語音庫體驗很差,所以想一個能搜索網上資源智能朗讀的東東,木有發現好的,就自己寫一個吧!
(Microsoft Edge閱讀視圖朗讀示例)
小說資源獲取(scrapy爬蟲獲取biquge資源)
1.scrapy環境搭建,這里就不說了,網上資源一堆,我也收錄了一個,不知道可以看看,附帶一個scrapy經典的架構圖(轉載)
- 引擎(Scrapy)
用來處理整個系統的數據流處理, 觸發事務(框架核心) - 調度器(Scheduler)
用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什么, 同時去除重復的網址 - 下載器(Downloader)
用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的) - 爬蟲(Spiders)
爬蟲是主要干活的, 用於從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面 - 項目管道(Pipeline)
負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被發送到項目管道,並經過幾個特定的次序處理數據。 - 下載器中間件(Downloader Middlewares)
位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。 - 爬蟲中間件(Spider Middlewares)
介於Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。 - 調度中間件(Scheduler Middewares)
介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。
2.核心代碼編寫,按照爬蟲架構,來編寫爬蟲及各個中間件內容(見代碼)
爬蟲核心代碼(BiqugeSpider),最高爬取深度4層,同時定義了關鍵字和全文爬取2種策略
第1層 根據關鍵字搜索小說列表(默認函數parse),這里主要根據獲取分頁所有頁url,傳遞給下一層,小說的基本信息列表爬取層
代碼如下(這里xpath就不做介紹了,有興趣自己學習下):
def parse(self, response): index=0 #檢索小說(這里通過判斷關鍵字來進行全文或者關鍵字爬取) if self.p: for each in response.xpath("//*[@class='search-result-page-main']/a"): src=each.attrib['href'] yield scrapy.Request(self.domainUrl+ src, callback = self.ParsePage,dont_filter=False) else: for each in response.xpath("//*[@id='main']/div[1]/ul/li"): if index>0: src=each.xpath(".//span[2]/a")[0].attrib['href'] yield scrapy.Request(self.domainUrl+ src, callback = self.BookBasic,dont_filter=False) index=index+1
第2層 根據小說基本信息列表獲取每本小說的詳情url,傳遞給下一層,小說基本信息爬取層
圖和第一層類似,這里省略
代碼如下:
#獲取分頁內容,進行關鍵字深度爬取 def ParsePage(self,response): for each in response.xpath("//*[@class='result-list']/div"): src=each.xpath(".//div/a")[0].attrib['href'] yield scrapy.Request(self.domainUrl+ src, callback = self.BookBasic,dont_filter=False)
第3層 根據小說分頁列表獲取每本檢索到小說的基本信息,同時獲取章節列表url,傳遞給下一層,小說內容爬取層
代碼如下:(存儲了小說的名稱、作者、簡介信息)
#獲取小說基本信息,同時讓調度器爬取小說內容 def BookBasic(self,response): Id=uuid.uuid1() BookName=response.xpath("string(//*[@id='info']/h1)")[0].root.strip() Author=response.xpath("string(//*[@id='info']/p)")[0].root.strip().split(':')[1] imgSrc=response.xpath("//*[@id='fmimg']/img")[0].attrib['src'] img= requests.get(imgSrc) Image= img.content LatestChapter=response.xpath("string(//*[@id='info']/p[4]/a)")[0].root.strip() Desc1=response.xpath("string(//*[@id='intro'])")[0].root.strip() #查詢小說是否存在 resp= self.mysql.Query("select Id from BookBasic where BookName='{0}'".format(BookName)) if not resp: # 獲取小說信息 Id=uuid.uuid1() #插入數據 self.mysql.ExecuteSql("insert into BookBasic (BookName,Author,Image,LatestChapter,Desc1,Id) values(%s,%s,%s,%s,%s,%s)",(str(BookName),str(Author),Image,str(LatestChapter),str(Desc1),str(Id))) else: Id=resp[0][0] self.mysql.ExecuteSql("update BookBasic set LatestChapter=%s,Desc1=%s where Id=%s",(str(LatestChapter),str(Desc1),str(Id))) #查詢所有章節 list=self.mysql.Query("select Title from BookContent where Id='{0}'".format(str(Id))) index=0 listName=[] for each in response.xpath("//*[@id='list']/dl/dd"): try: src= each.xpath(".//a")[0].attrib['href'] title=each.xpath("string(.//a)")[0].root.strip() if self.InTable(list,title): print(title,"已經入庫...") elif title in listName: print(title,"重復章節...") else: request= scrapy.Request(self.domainUrl+ src, callback = self.BookContent,dont_filter=False) request.meta['id']=Id request.meta['Chapter']=index index=index+1 listName.append(title) yield request except Exception as e: print("analysis item error:"+item["title"]+ e);
第4層 根據小說章節url獲取小說內容信息,存儲小說內容,到此,小說內容全部爬取完成
代碼如下:
#爬取每個章節小說內容 def BookContent(self,response): try: item=BiqugespiderItem() item['Title']=response.xpath("string(//*[@class='bookname']/h1)")[0].root.strip() item['Content']=response.xpath("string(//*[@id='content'])")[0].root.strip() item['Id']= response.meta['id'] item['Chapter']= response.meta['Chapter'] yield item except Exception as e: print("login item error:"+item["title"]+ e);
3.持久化處理
上面說到了爬蟲的內容怎么獲取,下面說到存儲,當然這層可以自己處理,這里為了方便使用騰訊雲mysql進行存儲小說信息(mysql幫助類)
數據庫和表結構初始化:
#初始化數據庫 def IniMysql(self): #創建數據庫 conn=pymysql.connect(host=self.host,port=self.port,user=self.user,password=self.password,database='mysql') sql='create database if not exists Biquge' cursor=conn.cursor() cursor.execute(sql) cursor.close() conn.close() #創建表 conn=pymysql.connect(host=self.host,port=self.port,user=self.user,password=self.password,database=self.defaultDb) cursor=conn.cursor() sql= """CREATE TABLE IF NOT EXISTS BookBasic( BookName varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '書名', Author varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '作者', Image longblob NULL COMMENT '書封面', LatestChapter varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最新章節', Desc1 varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '內容描述', Id char(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主鍵', PRIMARY KEY (`Id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; """ cursor.execute(sql) sql="""CREATE TABLE IF NOT EXISTS BookContent( DId char(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, Title varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '詳情標題', Content longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '內容信息', Id char(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '書主鍵', Chapter varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '章節', SyncTime datetime(0) NULL DEFAULT NULL COMMENT '入庫時間', PRIMARY KEY (`DId`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; """ cursor.execute(sql) cursor.close() conn.close()
小說基本信息存儲代碼:
if not resp: # 獲取小說信息 Id=uuid.uuid1() #插入數據 self.mysql.ExecuteSql("insert into BookBasic (BookName,Author,Image,LatestChapter,Desc1,Id) values(%s,%s,%s,%s,%s,%s)",(str(BookName),str(Author),Image,str(LatestChapter),str(Desc1),str(Id))) else: Id=resp[0][0] self.mysql.ExecuteSql("update BookBasic set LatestChapter=%s,Desc1=%s where Id=%s",(str(LatestChapter),str(Desc1),str(Id)))
小說內容信息存儲代碼:
class BiqugespiderPipeline(object): def process_item(self, item, spider): DId=uuid.uuid1() SyncTime=datetime.datetime.now() mysql=MySqlComment() mysql.ExecuteSql("insert into BookContent values(%s,%s,%s,%s,%s,%s)",(str(DId),str(item['Title']),str(item['Content']),str(item['Id']),str(item['Chapter']),SyncTime)) return item
數據庫存儲結果:
(小說基本信息)
(小說內容信息)
4.代碼地址
只能朗讀客戶端下一章繼續寫,代碼github地址先附上