最近一直在忙于工作,当我知道这些都是借口,毕竟某些博主大佬深夜还在更新订阅号更新微博,或许这就是自制力的差距吧。不啰嗦了,今天要写的主要是一篇关于如何爬取“苏宁图书”,当然只是半成品,但是大部分问题都已经解决,在这里记录一下发生过的问题,以免再次出现
下面切入正题,这个爬虫我主要是想练一下Scrapy框架,因为是这两天才开始自学的,断断续续,后面学前面忘,很是苦恼
主要用到的框架是Scrapy
主要用到的类是:re,json,requests
废话不多说,直接上spider
# -*- coding: utf-8 -*-
import scrapy
#from SuningBook.items import SuningbookItem
import re
import requests
import json
class SuningbooklistSpider(scrapy.Spider):
name = 'SuningBooklist'
allowed_domains = ['list.suning.com']
start_urls = ['https://list.suning.com/emall/showProductList.do?ci=502320&pg=03&cp=0&cc=010']
def parse(self, response):
one_page_url = []
last_30_books = "https://list.suning.com/emall/showProductList.do?ci=502320&pg=03&cp=0&cc=010&paging=1"
li_list = response.xpath("//ul[@class='clearfix']/li")
for li in li_list:
# item = {}
#获取一页中前30本书详情页地址
one_page_url.append("https:" + li.xpath(".//a[@class='sellPoint']/@href").extract_first())
# print (one_page_url)
#获取一页中后30本书详情页地址
yield scrapy.Request(
last_30_books,
callback = self.last_30_books,
meta = {"one_page_url": one_page_url},
dont_filter=True
)
def last_30_books(self,response):
one_page_url = response.meta["one_page_url"]
print ('*' * 100)
li_list = response.xpath("//li")
for li in li_list:
one_page_url.append("https:" + li.xpath(".//a[@class='sellPoint']/@href").extract_first())
print (one_page_url)
for url in one_page_url:
item = {}
yield scrapy.Request(
url,
callback= self.purse_detail,
meta = {"item": item},
dont_filter= True
)
def purse_detail(self,response):
print ('*'*100)
item = response.meta["item"]
#获取图片
item["book_img"] = response.xpath("//div[@class='imgzoom-main']/a/img/@src").extract_first()
if item["book_img"] is None:
item["book_img"] = response.xpath("//div[@class='imgzoom-main']/a/img/@src2").extract_first()
#获取书名
item["book_title"] = response.xpath("//div[@class='proinfo-title']/h1/text()").extract_first()
item["book_title"] = item["book_title"].replace('\n', '').replace('\r', '').replace('\t', '').replace(' ', '')
#获取作者
item["book_author"] = response.xpath("//ul[@class='bk-publish clearfix']/li[1]/text()").extract_first()
item["book_author"] = item["book_author"].replace('\n', '').replace('\r', '').replace('\t', '').replace(' ', '')
#获取图书价格(包括定价和易购价)
# ninePartNumber = re.findall(r".*\"ninePartNumber\":\"(.*?)\",", response.body.decode())[0]
vendorCode = re.findall(r".*\"vendorCode\":\"(.*?)\",", response.body.decode())[0]
cmmdtyType = re.findall(r".*\"cmmdtyType\":\"(.*?)\",", response.body.decode())[0]
catenIds = re.findall(r"\"catenIds\":\"(.*?)\",", response.body.decode())[0]
partNumber = re.findall(r"\"partNumber\":\"(.*?)\",", response.body.decode())[0]
weight = re.findall(r"\"weight\":\"(.*?)\"", response.body.decode())[0]
# print (vendorCode, cmmdtyType, catenIds, partNumber, weight)
item["ref_price"], item["net_price"] = self.book_price(vendorCode, cmmdtyType, catenIds, partNumber, weight)
#获取出版社
item["book_product"] = response.xpath("//ul[@class='bk-publish clearfix']/li[2]/text()").extract_first()
if item["book_product"] == None:
item["book_product"] = None
#如果没有出版商,在这里获取出版时间
item["book_sell_date"] = response.xpath("//ul[@class='bk-publish clearfix']/li[2]/span[2]/text()").extract_first()
else:
item["book_product"] = item["book_product"].replace('\n', '').replace('\r', '').replace('\t', '').replace(' ', '')
#如果有出版商,在这里获取出版时间
item["book_sell_date"] = response.xpath("//ul[@class='bk-publish clearfix']/li[3]/span[2]/text()").extract_first()
item["book_sell_date"] = item["book_sell_date"].replace('\n', '').replace('\r', '').replace('\t', '').replace(' ', '')
yield item
# print (item)
#将数据保存到txt中
# with open(r"D:\Python-start\python-project\python-program\SuningBook\SuningBook\run-result\result.txt", 'a', encoding = 'utf-8') as f:
# f.write(str(item))
# f.write('\n')
# print ('保存成功')
def book_price(self, vendorCode, cmmdtyType, catenIds, partNumber, weight):
price_url = 'https://pas.suning.com/nspcsale_0_'+ partNumber + '_' + partNumber +'_'+ vendorCode +'_10_010_0100101_502282_1000000_9017_10106_'+ cmmdtyType +'___'+ catenIds +'_'+ weight +'___.html'
print (price_url)
response = requests.get(price_url)
json_str = response.content.decode().replace('pcData(', '').replace(')', '')
json_dict = json.loads(json_str)
# print (json_str)
return (json_dict['data']['price']['saleInfo'][0]['refPrice'], json_dict['data']['price']['saleInfo'][0]['netPrice'])
首先说明一下,这里之所以说是半成品,因为我现在只是实现了抓取一个页面上的图书及详细内容等,还没有让页面循环起来,不过这部分已经想好要怎么去做了,但这并不是最难的部分
下面详细记录一下遇到的问题:
1.第一个问题:苏宁图书虽然在Elements中可以看到全是在一个页面中显示的,但是在实际的Network中,我们只能在相应的Response中找到一部分图书,那么剩下的部分呢?
由于苏宁图书一页显示的是60本,经过对比发现,能够在直接请求URL的响应中找到的只有30本,而且是前30本,那么后30本在哪呢?经过搜索,我发现后30本藏在了一个名叫showProductlist的文件中,而这个文件的请求地址是这样的:https://list.suning.com/emall/showProductList.do?ci=502320&pg=03&cp=0&il=0&iy=0&adNumber=0&n=1&ch=4&prune=0&sesab=ACBAAB&id=IDENTIFYING&paging=1&sub=0
经过删删减减,最终能够正常响应的最精简地址为:
https://list.suning.com/emall/showProductList.do?ci=502320&pg=03&cp=0&cc=010&paging=1
细心的我们可以明显地发现这个地址中一定是存在某些规律的,经过尝试后我发现,这个地址中,cp代表着的页码(这里从0开始),paging代表着一页中的上半页或者下半页(即0代表前30本书,1代表着后30本书),对于pg和cc还没有研究明白,后面会在研究明白后不上,这里先留着BUG1
OK,既然了解到规律,那么我们可以找到其实页面的地址,这里就不写明了,这算是遇到的第一个问题,不算麻烦
2.第二个问题:很多我们想要的信息,比如:作者,出版社,出版时间,简介等信息都在响应中可以找到,而且很好提取,但是最主要的一个信息【价格】在响应中并没有找到,那么在哪呢?
苏宁图书中存在两个价格,一个是定价,一个是易购价,这两个价格在目标链接响应中都找不到。所以只能从别的文件入手,我随机挑了一本书,将定价和易购加分别在所有的响应文件中进行检索,差不多每个关键词能找到5-7个相关联的文件,很好,这样已经将范围缩小很多了。
接下来我一个个翻看这7个文件,很幸运,在一个名叫nspcsale后面接一大串数字符号的文件中,找到了我想要的内容,而且两个价格是在一起的,一个是"refPrice":"56.0"(这个显然是定价),一个是"netPrice":"45.00"(这个就是易购价),为了证实确实如此,我后面又找了多本书,现象确实是这样的,也就是说这个nspcsale后面接一大串数字符号的文件中存放的至少有我们需要的信息,只要我们可以成功请求到这个链接,就可以轻松拿到书的价格,为什么说轻松呢?因为他是个类json字符串,只要稍加修改,就可以成为一个真正的json字符串,之后的事情就不用多说了吧【手动哈哈】
相信大家对上面那个文件的名字一定十分反感,什么叫做“nspcsale后面接一大串数字符号”,为什么我会这么说呢,因为他长成这样:
nspcsale_0_000000010674063778_000000010674063778_0070298971_10_010_0100101_502282_1000000_9017_10106_Z001___R9011194_0.01___.html?callback=pcData&_=1552205571188
而这个文件的求情地址,也只是在它名字的基础上加上了苏宁的基本网址头罢了(手动掀桌)
下面我们要弄清楚的就是那些乱七八糟的字符到底是什么,同样我将上面的链接做了精简,精简成下面这样:https://pas.suning.com/nspcsale_0_000000010674063778_000000010674063778_0070298971_10_010_0100101_502282_1000000_9017_10106_Z001___R9011194_0.01___.html
通过一个字段一个字段的检索,很幸运,这些字段中的某些值可以在最原始的响应中找到,具体的对应结果我就不在这里展示了,大家可自己找找。当然还有几个字段是固定的,或许是因为同一类型的书那几个字段是固定的,这也只是我的猜测,还没有来得及正式,后续会去证实一下,BUG2
但由于这是一个额外的请求,所以我在爬虫中只能单独写了一个函数,用来专门处理这个需要单独请求的url。问题2到这里也算是告一段落了
剩下的就是一些基础的问题了,比如数据类型,数据获取方式之类的,通过翻阅基础资料都得到了解决,特别是response.body.decode(),在这个问题上我耽误了一段时间,看来基础还是太薄弱了,接下来我会抽时间继续完成,然后解决掉上面的BUG,先暂时写到这里