先说说为什么写这个小demo吧,说起来还真的算不上“项目”,之前有一个朋友面试,别人出了这么一道机试题,需求大概是这样紫滴:1.给定任意京东商品链接,将该商品评论信息拿下,存入csv或者数据库
2.要求使用多任务来提高爬虫获取数据的效率
3.代码简洁,规范,添加必要注释
4.可以使用函数式编程,或者面向对象编程 看到上面四个简单的需求,层次高的童鞋可能就看不下去了,因为太简单了,这里本人的目的是给初学爬虫的工程师们。
让我们进入正题吧:step1: 选定任意商品详情url地址
这里我们以:Apple iPhone 7 Plus (A1661) 128G 黑色 移动联通电信4G手机 为例
step2: 按照如下图操作找到评论的url地址:
step3:分析接口数据可知:
step4: 找到目标url并确定数据后,分析目标url接口:
1、分析请求方式,由下图可以看出请求是一个get请求
2、分析url地址规律
注意为了确保找到url地址规律需要多对比不同商品下的评论地址,和同一商品不通分类的url地址
结论:由上面的url可知,同一商品下的不同分页的url只有page值不同,不同商品下的同一分页URL地址,只有productId不同,callback该参数可以省略。
主要获取商品的评论数据如下: 用户头像、用户昵称、星级、内容、产品信息、评论图片地址、评论视频地址、发布时间。
下面开始撸代码:代码思路比较简单step1:导入相关模块
import requests
import re,json
from requests.exceptions import HTTPError,Timeout,RequestException,ProxyError,ConnectTimeout
from concurrent.futures import ThreadPoolExecutor
import csv,pymysql,threading
- step2: 定义一个类JdSpider,完成爬虫主体代码 在__init__初始化方法中添加相关属性
class JdSpider(object):
def __init__(self,start_url):
""":param start_url: 商品url地址"""
#设置商品url地址
self.start_url = start_url
#创建csv文件
self.csv_file = open('jd.csv','a+')
#csv文件头部用户头像headerUrl、用户昵称nickName、星级startLevel、内容content、产品信息productInfo、评论图片地址commentImages、评论视频地址videoUrl、发布时间publishTime。
fileNames = ['headerUrl','nickName','startLevel','content','productInfo','commentImages','videoUrl','publishTime',]
#创建csv文件句柄
self.writer = csv.DictWriter(self.csv_file,fieldnames=fileNames)
#写入csv文件头部
self.writer.writeheader()
#创建数据库链接
self.mysql_client = pymysql.Connect(
host='127.0.0.1', user='root', password="1314",
database='jd', port=3306,charset='utf8'
)
#创建游标
self.cursor = self.mysql_client.cursor()
# 实例化一个lock(互斥锁)
self.myLock = threading.Lock()
self.satrt_request(self.start_url)
- step3: start_request :根据商品url地址发起请求获取响应结果,提取商品的id以及能获取的最大页码数量,拼接商品评论url地址,并将所有请求任务添加进线程池。
def start_request(self,start_url):
#获取商品的id
# match:根据正则表达式,从字符串中提取符合正则表达式的结果,
# 从起始位置开始匹配,如果不符合正则规则,直接返回None,单次
# 匹配
# search:根据正则表达式,从字符串中提取符合正则表达式的结果
# 从整个字符串中匹配符合正则规则的子串,有结果直接返回,否则返回
# None,单次匹配
#/100000177760.html#comment
prodect_id = re.search('\d+',start_url).group()
firstComUrl = "/comment/productPageComments.action?callback=&productId=%s&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1" % prodect_id
response_text = self.send_request(firstComUrl)
if response_text:
# print('获取到了数据',response_text)
json_data = json.loads(response_text)
#获取能请求评论的最大页码数量
maxPage = int(json_data['maxPage'])
print('能够获取的最大页码数量',maxPage)
#将任务添加线程池中
pool = ThreadPoolExecutor(10)
for page in range(maxPage):
comUrl = "/comment/productPageComments.action?callback=&productId=%s&score=0&sortType=5&page=%s&pageSize=10&isShadowSku=0&fold=1" % (prodect_id,str(page))
result = pool.submit(self.send_request,comUrl)
result.add_done_callback(self.parse_comments)
pool.shutdown()
- step4: send_request该方法根据url地址使用requests发送请求,返回请求的响应结果
def send_request(self,url,headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'}):
try:
response = requests.get(url,headers=headers,timeout=10)
if response.status_code == 200:
print('请求成功',response.status_code)
return response.text
except (HTTPError,Timeout,RequestException,ProxyError,ConnectTimeout) as err:
print(err)
return None
- step5: parse_comments解析商品评论接口返回的评论数据,注意这里返回的是一个json字符串,需要使用json.loads将json字符串转换为python数据类型然后取值
def parse_comments(self,future):
response_text = future.result()
if response_text:
#解析数据
comments = json.loads(response_text)['comments']
for comment in comments:
commentInfo = {}
#姓名
commentInfo['nickName'] = comment['nickname']
#内容
commentInfo['content'] = comment['content']
#其他数据依次获取,给你们留点写代码的机会
.......
#存储数据
self.save_db_to_csv(commentInfo)
self.save_db_to_db(commentInfo)
- step6:将数据保存到csv文件
def save_db_to_csv(self,commentInfo):
#将数据写入csv文件
self.myLock.acquire()
self.writer.writerow(commentInfo)
self.myLock.release()
- step7:将数据保存到数据库
def save_db_to_db(self,commentInfo):
#将数据写入数据库
sql = """INSERT INTO jingdong (%s)VALUES (%s)""" % (','.join(commentInfo.keys()),','.join(['%s']*len(commentInfo)))
print(sql,list(commentInfo.values()))
#python中线程安全,
# 加锁
print(self.myLock)
self.myLock.acquire()
try:
self.cursor.execute(sql,list(commentInfo.values()))
mit()
print('插入数据成功')
except Exception as err:
self.mysql_client.rollback()
print(err)
# 解锁
self.myLock.release()
- step9:运行调用程序:
if __name__ == "__main__":
start_url = '/5089253.html'
jdSpider = JdSpider(start_url)
结果如下图所示:
上面代码中只获取了两个字段,其他字段大家可以自行获取了。