python爬虫--获取天猫店铺商品价格及销量


(一)项目目标

1. 获取天猫店铺 “探路者官方旗舰店” 所有商品的名称、价格以及销量。

说明:本次项目目标是从一个热门店铺排行榜中随机选择的,没有任何针对性的含义。

该排行榜的网址为: http://www.xlphb.cn/index.php?c=shop


2. 该店铺的截图如下:



3. 左上角有一个 “所有商品” 的链接,点击进入如下截图:




4. 下方截图可以看到数据仍然是分页排列的,一共有14页的数据。



(二)网页分析

1. 首先还是打开charles,刷新页面,通过charles的搜索功能,找到目标数据的请求



2. 确认该请求是否全部包含目标数据

从下方截图可以看到,返回的数据格式是html,经手动确认,该请求包含该页的所有商品信息。



3. 分析请求的具体情况

从下方截图可以看到,该请求的具体信息是:

url: https://toread.tmall.com/i/asynSearch.htm?_ksTS=1529821691770_124&callback=jsonp125&mid=w-18307703560-0&wid=18307703560&path=/search.htm&search=y&spm=a1z10.3-b-s.w4011-18307703560.430.4ee0605f0KyPWs&scene=taobao_shop&pageNo=3&tsearch=y

请求类型: GET



4. 可以看到,这个请求有非常多的query参数。

经手动测试,有一些参数即使没有,也可以拿到数据,最终精简到如下url:

https://toread.tmall.com/i/asynSearch.htm?mid=w-18307703560-0&pageNo=2

注意,最后一个pageNo是指的页数,这样我们就可以直接通过改变pageNo,就能获得到不同页面的数据了。


5. 然后就是对页面数据进行解析时有一个坑。

一般来讲,我比较擅长使用css和正则表达式语法来选择数据,但是请看以下截图:


目标数据就在这些div和dl里面,但是淘宝设置的class 使用了这样的形式 "\"item" \", 这样我在用pyquery解析的时候总是要么要错,要么拿到数据。

但是没关系,下面的代码中,我使用了xpath语法来选择,躲开这个坑。


6. 网页基本分析完毕,请求只有一个,比较简单。但是要注意一下两点:

- 不知道淘宝的具体规则是什么,发送一个上面的请求并不一定能够获取到目标数据,但是只要重复不断发送,就能获取到。

- headers一定要写全。


(三)核心代码实现

1. 一些需要使用的模块和常量

import requests
from requests.exceptions import RequestException
from scrapy.selector import Selector
import csv
import random
from requests.exceptions import ConnectionError

s = requests.session()

# csv结果文件的存储文件名
filename = '爬取结果.csv'

# 定义代理池url,可以从代理池项目文件中找到接口
PROXY_POOL_URL = 'http://127.0.0.1:5555/random'


2. 随机切换User-Agent:

我是在项目中加入了一个agent.txt文件,里面存储了一些User-Agent可供使用。

# ag作为开关,仅第一次读取,之后就从ag里面拿
ag = None
def change_agent():
	global ag

	if not ag:
		with open('agent.txt') as f:
			ag = f.readlines()
	
	return ag[random.randint(1,866)].strip()


3. 使用代理池

代理池的代码就不贴出来了,我是从github找的其他大神写的,从公开渠道获取免费代理后存储入redis的一个项目。

下面的代码使用前,我已经打开了redis和代理池的代码。

# 定义获取代理的函数
def get_proxy():
	"""从代理池中取出一个代理"""
	try:
		response = requests.get(PROXY_POOL_URL)
		if response.status_code == 200:
			return response.text
		return None
	except ConnectionError:
		return None

def build_proxy():
	"""将代理池中取出的代理构造成完整形式"""
	proxy = get_proxy()
	if proxy:
		return {
			'http': 'http://' + proxy,
			#'https': 'https://' + proxy
			}
	else:
		return None


4. 上面网页分析的部分有说到,发送该请求并不一定会获取到目标数据,如果没有获取到,需要重新发送。

这里定义个一个辅助函数,用于判断获取到的网页是否包含目标数据。

注意这里用的scrapy库里面的Selector模块,方便使用xpath语法选取数据。

当然还有其他库可以使用xpath语法,我对scrapy比较熟悉,所以使用这个。

def decide_if_loop(html):
	"""通过解析要拿到的页面第一个数据,判断是否拿到真正的页面,如果假的页面,就返回False"""
	selector = Selector(text=html)
	data = selector.xpath('/html/body/div/div[3]/div[1]/dl[1]/dd[2]/a/text()').extract_first()
	return False if not data else selector

5. 下面是获取页面的函数:

 注意该函数内,调用了上面的切换User-Agent 和 使用代理池的函数。

def get_page(url):
	"""
	1. user-agent 不断切换
	2. 直接使用代理池中的代理来请求
	"""
	headers = {
            'User-Agent':change_agent(),
            'Referer':'https://toread.tmall.com',
            'accept':'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01',
            'x-requested-with':'XMLHttpRequest',
            'accept-encoding':'gzip, deflate, br',
            'accept-language':'zh-CN,zh;q=0.9,en;q=0.8'
	}

	try:
		response = s.get(url, headers=headers, verify = False, proxies=build_proxy())
	except:
		response = s.get(url, headers=headers, verify = False)

	if response.status_code == 200:
		return response.text

	else:
		print("请求错误:{}".format(response.status_code))


6. 获取到真实页面之后,就需要进行页面解析

注意传入的参数是selector,就是前面的判断是否需要循环的函数中的返回值。

因为该函数内已经对页面进行解析后得到selector了,所以这里就不再重复,直接传入使用。

def parse_detail(selector):
	"""从拿到的真实页面中,解析出商品名,销量和价格"""

	data = []
	# 两个for循环解析一个html页面
	for i in range(1,13):
		for j in range(1, 6):
			title = selector.xpath('/html/body/div/div[3]/div['+str(i)+']/dl['+str(j)+']/dd[2]/a/text()').extract_first()
			price = selector.xpath('/html/body/div/div[3]/div['+str(i)+']/dl['+str(j)+']/dd[2]/div/div[1]/span[2]/text()').extract_first()
			num = selector.xpath('/html/body/div/div[3]/div['+str(i)+']/dl['+str(j)+']/dd[2]/div/div[3]/span/text()').extract_first()
			# 这个判断用于防止最后一页商品不全时,或者页面出现任何错误,值可能为空的情况
			if title and price and num:
				data.append([title.strip(), price.strip(), num.strip()])
	return data


7. 下面定义了两个函数,将解析到的数据存储本地csv文件。

def save_to_csv(rows, filename):
	if rows:
		with open(filename, "a") as f:
			f_csv = csv.writer(f)
			for row in rows:
				f_csv.writerow(row)	

def write_csv_headers(filename):
	csv_headers = ["商品名称", "价格", "销量"]
	with open(filename, "a") as f:
		f_csv = csv.writer(f)
		f_csv.writerow(csv_headers)	


8. 下面的一些函数将整个代码串在一起

def loop(url):

	html = get_page(url)
	selector = decide_if_loop(html)

	if not selector:
		loop(url)
	else:
		data = parse_detail(selector)
		save_to_csv(data, filename)


def get_urls():
	urls = []
	base_url = 'https://toread.tmall.com/i/asynSearch.htm?mid=w-18307703560-0&pageNo='
	for i in range(1, 15):
		urls.append(base_url + str(i))

	return urls


def main():
	write_csv_headers(filename)
	for url in get_urls():
		loop(url)

if __name__=="__main__":
	main()


(四)项目结果以及经验教训

经过不断的失败后重试,项目终于成功的获取了所有的14页的数据,共700多条,也就是该天猫店铺所有商品的名称、价格以及销量。展示截图如下:



经验教训:

淘宝的坑还是比较多的,在尝试的过程中:

1. 如果直接请求页面的url,可以直接获得到数据,但是全部是假数据;

2. 真实的请求也需要多次获取才能拿到目标数据;

3. 如果不使用代理池,自己的ip很容易被封掉。

4. 项目代码重用率比较低,如果要爬其他店铺的商品信息,如要重新进行分析。


本文仅供学习交流使用,请勿将其用于违法目的。



展开阅读全文

没有更多推荐了,返回首页