源碼:私信小編001 即可
最近有好幾個讀者私下問我:剛接觸Python、或打算要學習Python,不知道選什麼書比較合適,當時只根據自己的Python經驗和學習感受,給讀者推薦了一些自認為不錯的。但是,畢竟一個人接觸少,局限性太大,也許還有更多、更好的好書只是我沒有接觸過。於是就打算實際操作,通過爬蟲方式爬取某東上的書籍、通過數據來幫助大家更科學、更合理的選擇學習資料。
本文概要
說真的,在互聯網爆發的今天,想要找一本Python書那真的太簡單了,去某東或某寶,隨便敲一個Python,各種各樣的書籍撲面而來。好的是可選擇性多了,壞的是面對這些層次不齊的書,到底該選擇那一本,於是就有了這篇文章。
本篇文章分為上、下兩篇,今天是上篇,主要分享如何爬取書籍信息
再看看需要爬取的書籍指標:
環境搭建
在開始爬蟲之前,先要搭建一個爬蟲的虛擬環境。我是在window10系統,使用anconda進行環境管理,大家可以根據自己的系統和操作習慣自行選擇。
頁面分析
再明確了本文要爬取得參數之後,接下來爬蟲才算正式開始。首先,我們根據當前的需求,去某東商城找到對應的請求初始URL。本文主要是爬取python書籍,所以直接打開某東商城,在搜索欄直接輸入:python,就會出現我們需要的Python書籍:
1. 翻頁分析
這個只是搜索頁界面的url,當你在點擊頁面下面的第一頁時,你會發現第一頁面的url會反生變化,然後在點擊第二頁、第三頁的時候,url的如下:
# 前三頁的url https://search.jd.com/Search?keyword=python&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=python&page=1&s=1&click=0 https://search.jd.com/Search?keyword=python&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=python&page=3&s=57&click=0 https://search.jd.com/Search?keyword=python&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=python&page=5&s=117&click=0
對比這三個url,發現page、s這兩個參數隨著每次的翻頁,會發生變化。但是作為一個上過高中、學過等差數列的我,
一下子就找到了翻頁規律:2n-1
但是問題又來了,s是個什麼東西,大大的三個問號在我心裡。不過不要急,當試著去掉這個s參數時,界面居然沒有發生變化,這個參數是用來搞笑的嗎?不過對於爬蟲來說,這種對數據無影響的參數直接幹掉就是了(click這個參數也對數據沒影響,直接幹掉)。
於是,通用的請求的url如下:
https://search.jd.com/Search?keyword=python& enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=python &page=2n-1
2. 評論數和好評率
上邊搞定翻頁之後,接下來就是對每一本書參數進行提取了。右鍵查看源碼,隨機看一本書,書名、價格、詳情頁url、是否自營這4個參數直接在當前頁面響應中就可以提取的到,但發現評論數這個便簽顯示
是空的,某有錯。
▲查看源碼評論截圖▲
不過不要慌,問題不大。經分析評論相關的數據是通過Ajax請求動態載入的,對於這種情況,直接右鍵選擇檢查, 通過NetWork抓包分析,很快就找到了評論的請求路徑:
不過評論的url裡面包含referenceIds、_這兩個參數。對於抓包的url裡面referenceIds是所有書籍的sku_id,返回的是當前頁所有書的評論信息。而我在抓取數據的時候,為了保證評論數正確性和代碼的可讀性,請求只攜帶當前書籍sku_id,返回當前書籍的評論數和好評率。_參數是時間戳,很好處理。
輕輕鬆鬆搞定了評論
所以,通用的評論請求的url如下:
"https://sclub.jd.com/comment/productCommentSummaries.action?referenceIds={}&callback=jQuery5954857&_={}"
3. 詳情頁獲取銷量排名
自營書的銷量排名數據是在詳情頁面,所以這次需要進入詳情頁去一探究竟。還是剛才的操作,先看了一波源碼,毛都沒看到。不過結合之前評論的操作,繼續右鍵檢查,經過分析,排名參數也是通過Ajax請求動態載入的,
嗖嗖的就搞定了
經過分析和驗證,只需要將找到的url裡面參數skuId換成動態的就行。別的參數不用再做處理。
到這裡,最後一個參數也分析完畢,通用url如下:
https://c.3.cn/book?skuId={}&cat=1713,3287,3797&area=1_72_2799_0 &callback=book_jsonp_callback
Show Code
越往後書籍,書的銷量和質量都在下降,所以這裡只爬取前20頁的。經過上邊的分析,整個爬蟲的頁面分析已經逼逼完成,接下來就是代碼的展示。
好的,不逼逼了,直接操傢伙,上代碼:
import requests from lxml import etree from userAgent import USER_AGENT_LIST import random import time import re import os import csv from fake_useragent import UserAgent # 實例化一個ua對象 ua = UserAgent() class PythonBookSpider(object): """爬取京東商城前20頁的Python書籍""" def __init__(self): self.base = "https://search.jd.com/Search?keyword=python&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=python&page={}" self.comment_url = "https://sclub.jd.com/comment/productCommentSummaries.action?referenceIds={}&" "callback=jQuery5954857&_={}" self.rank_url = "https://c.3.cn/book?skuId={}&cat=1713,3287,3797&area=1_72_2799_0&callback=book_jsonp_callback" self.headers = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) " "Chrome/22.0.1207.1 Safari/537.1", "authority": "search.jd.com" } def _send_request(self, url): """ 發送請求,獲取響應 :param url: 請求路徑 :return: """ # self.headers["User-Agent"] = random.choice(USER_AGENT_LIST) self.headers["User-Agent"] = ua.random time.sleep(0.5) response = requests.get(url=url, headers=self.headers, timeout=5) return response def send_request(self, url): """主要是對請求響應進行處理""" try: response = self._send_request(url) except Exception as e: print("request error: {}".format(e)) return None if response.status_code != 200: content = None else: content = response.content return content def get_comment_count(self, sku_id): """獲取評論數""" print("comment url: {}".format(self.comment_url.format(sku_id, int(time.time())))) response = self.send_request(self.comment_url.format(sku_id, int(time.time()))) if not response: return "", "" # 響應的編碼方式可以在響應中查看 response = response.decode("GBK") good_rate = re.findall(""GoodRate":(d.d+)", response)[0] if re.findall(""GoodRate":(d.d+)", response) else "" commet_count = re.findall(""CommentCount":(d+)", response)[0] if re.findall(""CommentCount":(d+)", response) else "" # print(" good rate: {}".format(good_rate)) # print(" comment count: {}".format(commet_count)) return good_rate, commet_count def parse_book_rank(self, sku_id): """ 獲取京東自營書籍銷量排行榜名次 :param sku_id: int 書籍的sku_id :return: """ # bbook_jsonp_callback({"yn":2,"rank":86,"ebookId":0}) response = self.send_request(self.rank_url.format(sku_id)) if not response: return False, None print("b_rank:{}".format(response.decode())) b_rank = re.findall(r""rank":[-|0-9][0-9]*", response.decode()) b_rank = b_rank[0].split(":")[1] if b_rank else "" return True, b_rank def save_book_info(self, books_list): """ 保存書籍信息 :param files: 每一頁的書籍信息 """ if not os.path.exists("./PythonBookInfo.csv"): with open("./PythonBookInfo.csv", "a+", newline="") as file: writer = csv.writer(file) writer.writerow(["name", "price", "is_self_operated", "comment_counts", "good_comments_rate", "good_comments_rate", "sale_rank"]) with open("./PythonBookInfo.csv", "a+", newline="") as file: writer = csv.writer(file) for book_list in books_list: try: writer.writerows(book_list) except: continue def parse_index_page(self, response): """ 解析首頁的界面信息 :param response: type: str 搜索頁面的響應 :return: type: list 每一頁的全部書籍相關信息 """ index_selector = etree.HTML(response) books_list = index_selector.xpath(//div[@id="J_goodsList"]/ul/li) # 解析每一頁的書籍列表 py_bookinfo_list = [] for book in books_list: # 書籍詳情地址 b_url = book.xpath(.//div[@class="p-img"]/a/@href) # 圖書價格 b_price = book.xpath(.//div[@class="p-price"]//i/text()) # 賣家方式 b_seller = book.xpath(//div[@class="p-icons"]/i[1]/text()) # 書名稱 b_name = book.xpath(.//div[@class="p-name"]//em) # print("b_name: {}".format(b_name[0].xpath("string(.)"))) b_name = [] if not b_name else b_name[0].xpath("string(.)").strip() # 書的評論數:通過js載入的 sku_id = book.xpath(./@data-sku)[0] if not sku_id: continue good_rate, commet_count = self.get_comment_count(sku_id) if not all([b_url, b_price, good_rate, commet_count, b_name]): continue detail_url = "https:" + b_url[0] if not b_url[0].startswith("https") else b_url[0] # print("detail url:{}".format(detail_url)) # 如果是京東自營的話,在抓取對應的自營排名、出版社 if b_seller[0] == "自營": # 獲取書籍銷售排行榜名次 rank_response = self.parse_book_rank(sku_id) if not rank_response: continue b_rank = rank_response[1] b_seller = b_seller[0] # 獲取書籍出版社 # b_publisher = self.parse_detail_page(detail_url) else: b_rank = "" b_seller = "" py_bookinfo_list.append([[b_name, b_price[0], b_seller, commet_count, good_rate, b_rank, detail_url]]) return py_bookinfo_list def spider(self): """spider的主要邏輯業務""" for page in range(1, 21): # 1.請求搜索頁,獲取書籍列表頁面信息,這裡請求前20頁 first_response = self.send_request(self.base.format(2 * page - 1)) if not first_response: continue # 2.解析搜索頁書籍的相關信息 py_bookinfo_list = self.parse_index_page(first_response) if not py_bookinfo_list: continue # 3.保存爬取書籍信息 self.save_book_info(py_bookinfo_list) print("第 {}頁爬取完成".format(page)) print("抬頭 望天") if __name__ == __main__: py_spider = PythonBookSpider() py_spider.spider()
▲由於插入代碼之後格式亂,手動調整格式可能會出錯,最好是後台獲取源碼▲
整篇文章爬虫部分就已經完成了。接下來就是運行該文件,爬取書籍信息了。爬到的書籍一部分信息如下:
至此,這篇文章的上篇就分享到這裡,接下來會分享下篇,主要是通過對爬到的書籍進行分析,找出性價比更搞的書籍,幫助大家在選擇買書的時候可以多一份參考,少一分焦慮。
TAG:Python | 數據分析 |