看到这篇 文章 受的启发,干脆自己也来爬试试
一、准备工作
打开京东,搜索 文胸,就从这开始爬吧
后面的商品大概就没什么购买量了,所以爬个15页就差不多了。
分析下 url, 可以发现 page 控制页数,并且每次增加两页,所以可以很简单的写出我们要爬取15页的商品 url 列表。
由于每个购买者所购买的商品尺寸和型号存在评论里,所以我们得进去爬取每个商品的所有评论部分。
首先得获得每个商品的详细链接。
发现这些链接有的隐藏在 JS 内,所以用 selenium + BeautifulSoup 爬取
pages = set()
links = []
def get_Links(url):
global pages
global links
driver = webdriver.PhantomJS()
driver.get(url)
web_data = driver.page_source
soup = BeautifulSoup(web_data, 'lxml')
all_imgs = soup.find_all(class_="p-img")
for link in all_imgs:
lin = link.find_all('a')[0].get("href")
if lin not in pages:
links.append(lin)
pages.add(lin)
由于担心有的商品链接会重复,建了个集合,但实际上发现并没有重复的链接,集合和列表长度都一样。
得到了每个商品的链接后,(15页是900多个),接着需要分别爬取每个商品的评论中的尺寸信息。
在网页源代码中并没有发现我们想要信息,显然是存在 JS 内。
xhr 内并没有发现,反而是在 JS 内,找了挺久的发现了,如下图。
我们想要的信息存在 product 这里
复制下 Request URL,打开看看,果然我们想要的信息就在里面,尺寸,评论等。正则获取下就行
分析下 URL ,发现 page 是控制页数,product 是商品的 ID 号, 看了下每页都只有10个评论,所以 pagesize就是每页的页数。
那么我们只要用最大的评论数,除以10,就是 page 最大的页数了
既然 product 是 商品 ID 号,在第一步找出所有商品的链接时,商品的 ID 号就在 URL 里面,提取一下就能获得,再传入这里的 URL,就可以获得每个商品对应的评论。
那么评论数怎么获得,有很多方法。
首页就有评论数量,但也是存在 JS 里,用 selenium 显然浪费资源了,特别爬取这么多数据的时候
刷新下网页,发现在 JS 里有个 product summaries,这不是摘要的意思么
点进去看看,果然有评论数量,用正则提取下就行
分析下 URL, 最后面的是时间戳,删掉了并没什么关系,referrenced 后面跟的还是商品 ID,那么同上一样传入商品 ID 就可以获得评论数量了
二、完整代码
先获取所有商品的链接,再爬取每个商品的评论,写入文件就行,由于 excel 比较慢,就用 csv 吧。
出问题的地方可能很多,就用个大的异常统一处理吧
import requests
from bs4 import BeautifulSoup
import urllib
import csv
import codecs
from selenium import webdriver
from multiprocessing.dummy import Pool
import re
import os
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
}
def get_Links(url):
global pages
global links
driver = webdriver.PhantomJS()
driver.get(url)
web_data = driver.page_source
soup = BeautifulSoup(web_data, 'lxml')
all_imgs = soup.find_all(class_="p-img")
for link in all_imgs:
lin = link.find_all('a')[0].get("href")
if lin not in pages:
links.append(lin)
pages.add(lin)
def get_Title(url):
if re.match('http:', url) == None:
url = 'http:' + url
r = requests.get(url, headers=headers)
soup = BeautifulSoup(r.text, 'lxml')
title = soup.find(class_="sku-name").text.strip()
return title
def get_CommentCount(good_ID):
ur = 'http://club.jd.com/comment/productCommentSummaries.action?referenceIds='
url = ur + good_ID
r = requests.get(url)
count = re.findall(r'"CommentCount":(\d*),"',r.text)
return int(int(count[0])/10)
def wirte_Title():
csvfile = codecs.open('xz.csv', 'w+', encoding='utf-8')
title = '\ufeff商品名'
writer = csv.writer(csvfile)
data = (title, '尺寸', '评论', '时间')
writer.writerow(data)
csvfile.close()
def get_Comment(good_ID, nums, name):
ur = 'http://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv11&productId={productId}&score=0&sortType=6&page={page}&pageSize=10&isShadowSku=0'
nums = int(nums)
urls = (ur.format(productId=good_ID, page=i)for i in range(0, nums+1))
#p = Pool(4)
for url in urls:
try:
r = requests.get(url)
r.encoding = r.apparent_encoding
web_data = r.text
size_onepage = re.findall(r'"productSize":"(.*?)"', web_data)
comment_onepage = re.findall(r'"content":"([^<]*?)"',web_data)
time_onepage = re.findall(r'[^>]","creationTime":"([^<]*?)"',web_data)
if len(size_onepage) == len(comment_onepage) ==len(time_onepage):
csvfile = codecs.open('xz.csv', 'r+', encoding='utf-8')
csvfile.seek(0, os.SEEK_END)
writer = csv.writer(csvfile)
for size, comment, time in zip(size_onepage, comment_onepage, time_onepage):
data = (name, size, comment, time)
writer.writerow(data)
#print('Done ten')
csvfile.close()
except:
print('get infomation error')
if __name__ == '__main__':
urls = ("https://search.jd.com/Search?keyword=%E6%96%87%E8%83%B8&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&offset=4&page={}&s=1&click=0".format(i) for i in range(1,32,2))
pages = set()
links = []
#for url in urls:
#get_links(url)
pool = Pool(4)
pool.map(get_Links, urls)
pool.close()
pool.join()
wirte_Title()
for link in links:
try:
title = get_Title(link)
product_ID = re.findall(r'item.jd.com/(\d*).html', link)[0]
count = get_CommentCount(product_ID)
get_Comment(product_ID, count, title)
print('Done one good')
except:
print('get link error')
看看效果
爬了大概半小时,大概 5w+ 了
三、简单分析
好像ABCD之类也有细分的,嫌太麻烦了,就改了下代码,size 直接爬 ABCDEF了
import requests
from bs4 import BeautifulSoup
import urllib
import csv
import codecs
from selenium import webdriver
from multiprocessing.dummy import Pool
import re
import os
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
}
def get_Links(url):
global pages
global links
driver = webdriver.PhantomJS()
driver.get(url)
web_data = driver.page_source
soup = BeautifulSoup(web_data, 'lxml')
all_imgs = soup.find_all(class_="p-img")
for link in all_imgs:
lin = link.find_all('a')[0].get("href")
if lin not in pages:
links.append(lin)
pages.add(lin)
def get_Title(url):
if re.match('http:', url) == None:
url = 'http:' + url
r = requests.get(url, headers=headers)
soup = BeautifulSoup(r.text, 'lxml')
title = soup.find(class_="sku-name").text.strip()
return title
def get_CommentCount(good_ID):
ur = 'http://club.jd.com/comment/productCommentSummaries.action?referenceIds='
url = ur + good_ID
r = requests.get(url)
count = re.findall(r'"CommentCount":(\d*),"',r.text)
return int(int(count[0])/10)
def wirte_Title():
csvfile = codecs.open('xz_type.csv', 'w+', encoding='utf-8')
title = '\ufeff商品名'
writer = csv.writer(csvfile)
data = (title, '尺寸', '评论', '时间')
writer.writerow(data)
csvfile.close()
def get_Comment(good_ID, nums, name):
ur = 'http://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv11&productId={productId}&score=0&sortType=6&page={page}&pageSize=10&isShadowSku=0'
nums = int(nums)
urls = (ur.format(productId=good_ID, page=i)for i in range(0, nums+1))
#p = Pool(4)
for url in urls:
try:
r = requests.get(url)
r.encoding = r.apparent_encoding
web_data = r.text
size_onepage = re.findall(r'"productSize":"(.*?)"', web_data)
comment_onepage = re.findall(r'"content":"([^<]*?)"',web_data)
time_onepage = re.findall(r'[^>]","creationTime":"([^<]*?)"',web_data)
if len(size_onepage) == len(comment_onepage) ==len(time_onepage):
csvfile = codecs.open('xz_type.csv', 'r+', encoding='utf-8')
csvfile.seek(0, os.SEEK_END)
writer = csv.writer(csvfile)
for size, comment, time in zip(size_onepage, comment_onepage, time_onepage):
try:
sizetype = re.search('[A-Z]', size)
if sizetype:
s_type = sizetype.group()
data = (name, s_type, comment, time)
writer.writerow(data)
#print('Done ten')
except:
print('get_sizetype_error')
csvfile.close()
except:
print('get infomation error')
#print(url)
if __name__ == '__main__':
urls = ("https://search.jd.com/Search?keyword=%E6%96%87%E8%83%B8&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&offset=4&page={}&s=1&click=0".format(i) for i in range(1,32,2))
pages = set()
links = []
#for url in urls:
#get_links(url)
pool = Pool(4)
pool.map(get_Links, urls)
pool.close()
pool.join()
wirte_Title()
for link in links:
try:
title = get_Title(link)
product_ID = re.findall(r'item.jd.com/(\d*).html', link)[0]
count = get_CommentCount(product_ID)
get_Comment(product_ID, count, title)
print('Done one good')
except:
print('get link error')
#print(link)
爬了大概 30W+,把开始的 url 链接改多点能爬更多,但担心被京东封 IP ,就先爬这么多吧。
清洗下数据,直接用 excel 打开,在 size 列排序,有一些 M 的背心之类,居然还有 男士内裤也爬进去了一个,都删掉。
统计下 ABCDEFG的数量,做个饼图
A和B占了 80% 以上,D及以上不到 5%。
C占了12%。
感觉有意思做了下,数据并不是太严谨,娱乐一下哈