爬虫原理
爬虫需要做如下事情:
1. 模拟对服务端的Request请求;
2. 接收Response 内容并解析、提取所需信息;
简单来说,就是模仿浏览器浏览网页信息。
实战讲解
在淘宝首页输入商品数据,搜索出来的商品信息是ajax动态加载出来的,这样的信息再源代码的是找不到,于是爬取这些信息可以选择selenium或者找到这个js文件进行解析,本文这次是抓到这个js文件进行解析的,首先打开淘宝页面,本文以搜索python为例
下面我们来分析爬取的页面结构。利用re正则表达式可以得到想要的结果。
先来看看网页源码。
我们使用的是Chrome浏览器,在上述搜索页面中,按F12开发者工具。可以看到如下页面
我们选择搜索页面点击进去,
从Headers里面可以看到Request URL地址,可以看到使用的是GET请求。
https://s.taobao.com/search?q=python&imgfile=&js=1&stats_click=search_radio_all%3A1&initiative_id=staobaoz_20180305&ie=utf8
搜索首页面任意输入商品搜索,发现规律,其变化参数如上图Query String Parameters,我们重点关注其中两个:
q:代表输入的搜索词,用来作为查询关键词
initiative_id=staobaoz_20180305 内部参数,带时间戳的显示控制方式
其他的比如
stats_click= 点击方式
ie:显示编码方式
还有常见的
trackid=17 流量来源 17是钻展、1是直通车、2是淘客
isb= 是否商城
shop_type= 店铺类型
base64=1 手机端编码,支持功能手机
sort=排序方式,default是综合排序,renqi-desc是人气排名,sale-desc是销量排名,oldstart是新品排序
loc= 城市
paytype= 支付方式
topSearch=1 搜索显示方式
sid= 请求签名,手机端验证使用
from= 来源位置 spm是具体的点,一个页面什么位置,from是区块
ppath=属性
brand品牌
stats_click= 点击方式
tab=mall 区域 mall是天猫 old是二手 g是全球购
bucketid 内部参数,应该是显示控制方式
基于分析,我们定义一个简单搜索页面url
first_url = "https://s.taobao.com/search?imgfile=&js=1&stats_click=search_radio_all%3A1&ie=utf8"我
我们使用requests.get()用于请求目标网站,类型是一个HTTPresponse类型
使用带参数的GET请求:
先将参数填写在dict中,发起请求时params参数指定为dict
find_arg = {
'q': find_word,
'initiative_id': 'staobaoz_%s%02d%02d' % (t[0], t[1], t[2])
}
# 搜索页面url
# https://s.taobao.com/search?q=python&imgfile=&js=1&stats_click=search_radio_all%3A1&initiative_id=staobaoz_20180305&ie=utf8
first_url = "https://s.taobao.com/search?imgfile=&js=1&stats_click=search_radio_all%3A1&ie=utf8"
# url = 'https://s.taobao.com/search?q=python&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306'
# 发送请求
response = requests.get(first_url,params=find_arg)
html = response.text
接下来就是提取,筛选,清洗数据
翻页:
点击商品页面第二页,我们发现JS中多了很多个请求,如图,这里有隐藏着商品信息的js文件。注意,为了便于实时观察,我们可以先将上次的network信息clear(红色圓点旁边那个带斜杠的灰色圆圈)
同样观察Request URL,多试几次发现地址拼接规律。接下来我们就进行模拟请求发送。
首先找到获得这个文件的链接,如图,
https://s.taobao.com/search?data-key=s&data-value=44&ajax=true&_ksTS=1520221609561_908&callback=jsonp909&q=python&imgfile=&js=1&stats_click=search_radio_all%3A1&initiative_id=staobaoz_20180305&ie=utf8&bcoffset=4&ntoffset=0&p4ppushleft=1%2C48
参数有:
Query String Parameters
data-key:s
data-value:44
ajax:true
_ksTS:1520221609561_908
callback:jsonp909
q:python
imgfile:
js:1
stats_click:search_radio_all:1
initiative_id:staobaoz_20180305
ie:utf8
bcoffset:4
ntoffset:0
p4ppushleft:1,48
其实这个链接很复杂,但是我们可以修改,吧其他东西去掉,剩下这样的
https://s.taobao.com/search?data-key=s&;data-value=44&;ajax=true&;_ksTS=1479917597216_854&;callback&;q={},
大括号里面的是我们自己填充的搜索内容,访问修改过的网址跟hearers里面的那个网址产生的内容是一样的
,于是我们使用这个简化版的url
继续对淘宝的页面进行Inspect,发现每一框的店铺数据储存在g_page_config中的auctions中,并且g_page_config中的数据为json格式。
获取原始数据 (g_page_config以及auctions)
在Python中,可以通过专门的解析器来完成。通过其中的loads 和dumps 可以轻易的在json以及str之间做转换。将str转换为json格式,在通过pandas 的获取json中的数据信息。
每一框店铺的数据涵括非常丰富的店铺信息以及销售信息,我们可以收集宝贝分类(category)、评论数(comment_count)、宝贝位置(item_loc)、店铺名称(nick)、宝贝名称(raw_title)、原价格(reserve_price)、显示价格(view_price)、销量(view_sales)进行分析.
具体分析过程:
依然在F12页面,我们之前浏览的三Headers信息,现在我们点击Response,可以看到json格式的字符串,为了便于观察分析,我们将其拷贝到在线json解析工具,并转换。
在线转换:
由此一层层的结构关系显而易见。
最终结果展示:
获得数据后我们可以利用画图工具进行饼状图,直方图,折线图等各自图表分析。本文不作详细探讨,后续再聊。
全部代码如下:
# !/usr/bin/python3
# -*- coding:utf-8 -*-
__auther__ = 'gavin'
import requests
import re
import json
import time
from hashlib import md5
import xlwt
# 数据
DATA = []
t = time.localtime()
# 搜索关键字
find_word = 'python'
# 参数
find_arg = {
'q': find_word,
'initiative_id': 'staobaoz_%s%02d%02d' % (t[0], t[1], t[2])
}
# 搜索页面url
# https://s.taobao.com/search?q=python&imgfile=&js=1&stats_click=search_radio_all%3A1&initiative_id=staobaoz_20180305&ie=utf8
first_url = "https://s.taobao.com/search?imgfile=&js=1&stats_click=search_radio_all%3A1&ie=utf8"
# url = 'https://s.taobao.com/search?q=python&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306'
# 发送请求
response = requests.get(first_url,params=find_arg) #response.json()方法同json.loads(response.text)
html = response.text
# 提取,筛选,清洗数据
content = re.findall(r'g_page_config = (.*?)g_srp_loadCss',html,re.S)[0] # 正则表达式处理的结果是一个列表,取第一个元素(字典)
# 格式化,将json格式的字符串切片
content = content.strip()[:-1]
# 将json转为dict
content = json.loads(content)
# 借助json在线解析分析,取dict里的具体data
data_list = content['mods']['itemlist']['data']['auctions']
# 提取数据
for item in data_list:
temp = {
'title' : item['title'],
'view_price' : item['view_price'],
'view_sales' : item['view_sales'],
'view_fee' : '否' if float(item['view_fee']) else '是' ,
'isTmall' : '是' if item['shopcard']['isTmall'] else '否',
'area' : item['item_loc'],
'name' : item['nick'],
'detail_url' : item['detail_url'],
}
DATA.append(temp)
print(len(DATA)) # 36 首页有12条异步加载的数据 ,应该是48
# 保存一下cookie
cookie_ = response.cookies
# 首页有12条异步加载的数据
# url2 = 'https://s.taobao.com/api?_ksTS=1520072935603_226&callback=jsonp227&ajax=true&m=customized&sourceId=tb.index&q=python&spm=a21bo.2017.201856-taobao-item.1&s=36&imgfile=&initiative_id=tbindexz_20170306&bcoffset=0&commend=all&ie=utf8&rn=e061ba6ab95f8c06a23dbe5bfe9a5d94&ssid=s5-e&search_type=item'
ksts = str(int(time.time() * 1000))
url2 = "https://s.taobao.com/api?_ksTS={}_219&callback=jsonp220&ajax=true&m=customized&stats_click=search_radio_all:1&q=java&s=36&imgfile=&bcoffset=0&js=1&ie=utf8&rn={}".format(
ksts, md5(ksts.encode()).hexdigest())
# 发送请求
response2 = requests.get(url2, params=find_arg, cookies=cookie_)
html2 = response2.text
# print(html2)
data_list = json.loads(re.findall(r'{.*}',html2,re.S)[0])['API.CustomizedApi']['itemlist']['auctions']
# 提取数据
for item in data_list:
temp = {
'title' : item['title'],
'view_price' : item['view_price'],
'view_sales' : item['view_sales'],
'view_fee' : '否' if float(item['view_fee']) else '是' ,
'isTmall' : '是' if item['shopcard']['isTmall'] else '否',
'area' : item['item_loc'],
'name' : item['nick'],
'detail_url' : item['detail_url'],
}
DATA.append(temp)
print(len(DATA)) # +12 首页有12条异步加载的数据
# 翻页
get_args = {}
cookies = response2.cookies #更新一下cookies
for i in range(1,10):
ktsts = time.time()
get_args['_ksTS'] = "%s_%s" % (int(ktsts*1000),str(ktsts)[-3:])
get_args['callback'] = "jsonp%s" % (int(str(ktsts)[-3:]) + 1)
get_args['data-value'] = 44 * i
get_args['q'] = 'python'
#url = 'https://s.taobao.com/search?data-key=s&data-value=44&ajax=true&_ksTS=1520091448743_4613&callback=jsonp4614&q=python&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306&bcoffset=4&ntoffset=0&p4ppushleft=1%2C48&s=0'
url = "https://s.taobao.com/search?data-key=s&data-value=44&ajax=true&imgfile=&js=1&stats_click=search_radio_all%3A1&ie=utf8&bcoffset=4&ntoffset=0&p4ppushleft=1%2C48".format(
time.time())
if i > 1:
get_args['s'] = 44*(i-1)
r3 = requests.get(url,params=get_args,cookies = cookies)
html = r3.text
content = re.findall(r'{.*}',html,re.S)[0]
content = json.loads(content)
# print(content['mods']['itemlist']['data']['auctions'])
data_list = content['mods']['itemlist']['data']['auctions']
# 提取数据
for item in data_list:
temp = {
'title': item['title'],
'view_price': item['view_price'],
'view_sales': item['view_sales'],
'view_fee': '否' if float(item['view_fee']) else '是',
'isTmall': '是' if item['shopcard']['isTmall'] else '否',
'area': item['item_loc'],
'name': item['nick'],
'detail_url': item['detail_url'],
}
DATA.append(temp)
cookie_ = r3.cookies
print(len(DATA)) # +12 首页有12条异步加载的数据
# exit(0) # for test 1 times
# 分析
'''
# 画图
data1 = { '包邮':0, '不包邮':0}
data2 = {'天猫':0, '淘宝':0}
# 地区分布
data3 = {}
for item in DATA:
if item['view_fee'] == '否':
data1['不包邮'] += 1
else:
data1['包邮'] += 1
if item['isTmall'] == '是':
data1['天猫'] += 1
else:
data1['淘宝'] += 1
data3[ item['area'].split(' ')[0] ] = data3.get(item['area'].split(' ')[0], )
print(data1)
draw.pie(data1,'是否包邮')
draw.pie(data2,'是否天猫')
draw.bar(data3,'地区分布')
'''
# 持久化
f = xlwt.Workbook(encoding='utf-8')
sheet01 = f.add_sheet(u'sheet1',cell_overwrite_ok=True)
# 写标题
sheet01.write(0,0,'标题')
sheet01.write(0,1,'标价')
sheet01.write(0,2,'购买人数')
sheet01.write(0,3,'是否包邮')
sheet01.write(0,4,'是否天猫')
sheet01.write(0,5,'地区')
sheet01.write(0,6,'店名')
sheet01.write(0,7,'url')
# write data
for i in range(len(DATA)):
sheet01.write(i + 1, 0, DATA[i]['title'])
sheet01.write(i + 1, 1, DATA[i]['view_price'])
sheet01.write(i + 1, 2, DATA[i]['view_sales'])
sheet01.write(i + 1, 3, DATA[i]['view_fee'])
sheet01.write(i + 1, 4, DATA[i]['isTmall'])
sheet01.write(i + 1, 5, DATA[i]['area'])
sheet01.write(i + 1, 6, DATA[i]['name'])
sheet01.write(i + 1, 7, DATA[i]['detail_url'])
f.save(u'搜索%s的结果.xls' % find_word) # 'python'
本文采用的面向过程编程,主要用于学习爬虫,分析数据等。若在实战项目中,可以利用函数式编程或者面向对象风格。
附:百度爬图
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : spider_baidu.py
@Contact : 476423448@qq.com
@License : (C)Copyright 2020-2021, AIgroup-KPCQ
爬取百度图片 爬虫 输入关键字和页数即可
@Modify Time @Author @Version @Desciption
------------ ------- -------- -----------
7/17/21 2:21 PM gavin 1.0 None
'''
import requests
from threading import Thread
import re
import time
import hashlib
import os
import requests
import os
import urllib
class Spider_baidu_image():
def __init__(self,path):
self.url = 'http://image.baidu.com/search/acjson?'
self.headers = {
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36' }
self.headers_image = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'Referer': 'https://image.baidu.com/search/index?tn=baiduimage&ct=201326592&lm=-1&cl=2&ie=gb18030&word=%D3%EA%DA%A1&fr=ala&ala=1&alatpl=normal&pos=0'
}
self.keyword = input("请输入搜索图片关键字:")
self.paginator = int(input("请输入搜索页数,每页30张图片:"))
self.path = path
# self.paginator = 50
# print(type(self.keyword),self.paginator)
# exit()
def get_param(self):
"""
获取url请求的参数,存入列表并返回
:return:
"""
keyword = urllib.parse.quote(self.keyword)
params = []
for i in range(1, self.paginator + 1):
params.append(
'tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord={}&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=&hd=1&latest=0©right=0&word={}&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&force=&cg=star&pn={}&rn=30&gsm=78&1557125391211='.format(
keyword, keyword, 30 * i))
return params
def get_urls(self, params):
"""
由url参数返回各个url拼接后的响应,存入列表并返回
:return:
"""
urls = []
for i in params:
urls.append(self.url + i)
return urls
def get_image_url(self, urls):
image_url = []
for url in urls:
json_data = requests.get(url, headers=self.headers).json()
json_data = json_data.get('data')
for i in json_data:
if i:
image_url.append(i.get('thumbURL'))
return image_url
def get_image(self, image_url):
"""
根据图片url,在本地目录下新建一个以搜索关键字命名的文件夹,然后将每一个图片存入。
:param image_url:
:return:
"""
#cwd = os.getcwd()
file_name = os.path.join(self.path, self.keyword)
if not os.path.exists(self.keyword):
os.makedirs(file_name)
for index, url in enumerate(image_url, start=1):
file_save = os.path.join(file_name,'{}.jpg'.format(index))
with open(file_save, 'wb') as f:
f.write(requests.get(url, headers=self.headers_image).content)
if index != 0 and index % 30 == 0:
print('{}第{}页下载完成'.format(self.keyword, index / 30))
def __call__(self, *args, **kwargs):
params = self.get_param()
urls = self.get_urls(params)
image_url = self.get_image_url(urls)
self.get_image(image_url)
# if __name__ == '__main__':
# spider = Spider_baidu_image()
# spider()
class BaiDu(object):
"""
爬取百度图片
"""
def __init__(self, name, page, path):
self.start_time = time.time()
self.name = name
self.page = page
#self.url = 'https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&rn=60&'
self.url = 'https://image.baidu.com/search/acjson'
self.header = {'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36' }# 添加为自己的
self.num = 0
self.path = path
def queryset(self):
"""
将字符串转换为查询字符串形式
"""
pn = 0
for i in range(int(self.page)):
pn += 60 * i
name = {'word': self.name, 'pn': pn, 'tn':'resultjson_com', 'ipn':'rj', 'rn':60}
url = self.url
self.getrequest(url, name)
def getrequest(self, url, data):
"""
发送请求
"""
print('[INFO]: 开始发送请求:' + url)
ret = requests.get(url, headers=self.header, params=data)
if str(ret.status_code) == '200':
print('[INFO]: request 200 ok :' + ret.url)
else:
print('[INFO]: request {}, {}'.format(ret.status_code, ret.url))
response = ret.content.decode()
img_links = re.findall(r'thumbURL.*?\.jpg', response)
links = []
# 提取url
for link in img_links:
links.append(link[11:])
self.thread(links)
def saveimage(self, link):
"""
保存图片
"""
print('[INFO]:正在保存图片:' + link)
m = hashlib.md5()
m.update(link.encode())
name = m.hexdigest()
ret = requests.get(link, headers = self.header)
image_content = ret.content
if not os.path.exists(self.path + self.name):
os.mkdir(self.path + self.name )
filename = self.path + self.name + '/' + name + '.jpg'
with open(filename, 'wb') as f:
f.write(image_content)
print('[INFO]:保存成功,图片名为:{}.jpg'.format(name))
def thread(self, links):
"""多线程"""
self.num +=1
for i, link in enumerate(links):
print('*'*50)
print(link)
print('*' * 50)
if link:
# time.sleep(0.5)
t = Thread(target=self.saveimage, args=(link,))
t.start()
# t.join()
self.num += 1
print('一共进行了{}次请求'.format(self.num))
def __del__(self):
end_time = time.time()
print('一共花费时间:{}(单位秒)'.format(end_time - self.start_time))
def main():
# names = ['冰雹', '降雪', '降雨', '结冰', '露', '霜', '雾霾', '雾凇', '雨凇']
# for name in names:
name = '雨凇'
page = 10
baidu = BaiDu(name, page, '/media/gavin/home/gavin/DataSet/tmp')
baidu.queryset()
if __name__ == '__main__':
#main()
spider = Spider_baidu_image('/media/gavin/home/gavin/DataSet/tmp')
spider()