0分析:
要爬取的网址:https://book.jd.com/,本来想要爬取图书分类下面的特色分类,文学综合馆,童书馆,一直到文化/周边/娱乐/等好像是17/18个分类。随着工作的进行,发现原来的某三十天学会python爬虫的教程有些失效了(尴尬的一批),网址有了很大的改变,最后我们决定暂时爬取一个文学综合馆(我是喜欢文学的,哈哈)。
1继续分析:
点击进入文学综合馆,文学综合馆下面有五类分别是 小说、文学、青春文学、动漫、传记。五个类下面又有很多具体的分类(例如小说下面有 侦探悬疑 科幻 官场 历史 玄幻奇幻 都市 军事 社会 外国小说 世界名著 当代小说 惊悚恐怖 情感家庭 作品集),然后进入小说下面的侦探悬疑,发现才是具体的每本图书。
我们要爬的内容是每本图书的(第一频道(小说、文学、青春文学、动漫、传记中的之一),第二频道(侦探悬疑 科幻 官场 历史 玄幻奇幻 都市 军事 社会 外国小说 世界名著 当代小说 惊悚恐怖 情感家庭 作品集中的一个))、图书名、价格、评论数、作者、出版商、链接。(本想着把商家一块爬取了,后来发现商家太麻烦了(亲测麻烦))
首先我们要爬取文学馆的地址,然后下面的五类的地址,再然后获取五类下面的若干个类的地址,然后爬取具体的图书页(每种图书页都有200多页)
涉及到的内容有xpath解析,正则表达式,图书的具体价格、评论数量网页上没有我们需要抓包分析urllib.request.urlopen() 方式的浏览器伪装,requests方式的浏览器伪装,中间还涉及到怎么在pycharm上调试scrapy程序(可以参考我的另一篇博客)后面还需要把数据存入到数据库。
2实际操作
打开pycharm 创建项目
在终端输入scrapy stratproject jingdong,然后 cd jingdong 创建爬虫文件 scrapy genspider -t basic goods jd.com
然后使用pycharm打开我们创建的爬虫文件。常用的scrapy命令(下面的博客写的挺好的,整体框架都有,这里我们只讲具体操作)https://blog.csdn.net/weixin_34029949/article/details/90807568
(1)在items.py定义我们要爬取的的字段(代码中有注释)
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class JingdongItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 频道号
# pdnum = scrapy.Field()
# 第一个频道
pd1 = scrapy.Field()
# 第二个频道
pd2 = scrapy.Field()
# 图书名
bookname = scrapy.Field()
# 价格
price = scrapy.Field()
# 评价数
commentdata = scrapy.Field()
# 作者
author = scrapy.Field()
# 出版社
publish = scrapy.Field()
# # 店家
# seller = scrapy.Field()
#链接
link=scrapy.Field()
(2)在goods.py下面编写我们爬虫代码
import scrapy
from cq_land.items import CqLandItem
class TudinetSpider(scrapy.Spider):
name = 'tudinet'
allowed_domains = ['tudinet.com']
start_urls = ['https://www.tudinet.com/market-252-0-0-0']
def parse(self, response):
item=CqLandItem()
item['title']=response.xpath("//div[@class='land-l-bt']/text()").extract()
item['list_time']=response.xpath("//div[@class='land-l-cont']/dl/dd/p[1]/text()").extract()
return item
例子:上面的爬虫文件是针对单个网页,但实际上我们爬虫多半是需要针对整个网站的所有土地转让信息进行爬取的,因此,我们根据网站翻页的变化,一共有100页可以供我们爬取。因此,我们可以在开头对start_urls进行重新定义,用以爬取这100个页面。
这里有2种处理方式,第一种处理方式,就是直接把这100个url作为列表放到start_urls 中。其他的就不用改动了。
第二种处理方式,就是重新定义start_requests函数。实际上定义这个函数就是把start_urls列表里面的地址用函数生成,然后再通过callback参数设置回调函数,让parse函数来处理Request产生的结果。
(这里的两种方式是我转载文章里面的一段:https://www.jianshu.com/p/e2b79a85afc3)
我们这里采用第二中方法
# -*- coding: utf-8 -*-
import scrapy
from urllib import request
import re
import random
from jingdong.items import JingdongItem
from scrapy.http import Request
import requests
from lxml import etree
class GoodsSpider(scrapy.Spider):
name = 'goods'
allowed_domains = ['jd.com']
# start_urls = ['http://jd.com/']
headers = {
"Accept-Encoding": "gzip, deflate, sdch, br",
"Accept-Language": "zh-CN,zh;q=0.8",
"Connection": "keep-alive",
"Referer:https": "//channel.jd.com/1713-3258.html",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"
}
def start_requests(self):
# 构建用户代理池
ua_pool = [
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1 QQBrowser/6.9.11079.201',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)',
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0'
]
# req1 = request.Request('https://book.jd.com/')
# req1.add_header("User-Agent", random.choice(ua_pool))
# alldata = request.urlopen(req1).read().decode('utf-8', 'ignore')
# pat1 = '[^"\\/]+[^"]*(?=","ANCHOR")'
# result1 = re.compile(pat1).findall(alldata)
# print(result1)
allurl = []
# 只爬取文学馆
# string1 = result1[1].replace('\/', '/')
# string2 = "https:" + string1
string2='https://channel.jd.com/p_wenxuezongheguan.html'
req2 = request.Request(string2)
req2.add_header("User-Agent", random.choice(ua_pool))
alldata = request.urlopen(req2).read().decode('utf-8', 'ignore')
# print(alldata)
e = etree.HTML(alldata)
zz = e.xpath('//div[@class="ext"]//a/@href')
for i in range(len(zz)):
if zz[i][0] == '/':
currenturl = 'https:' + zz[i]
allurl.append(currenturl)
else:
allurl.append(currenturl)
# print(allurl)
# 网页去重
all = list(set(allurl))
# 获取每个频道有多少页码
pages = []
for i in range(len(all)):
currenturl = all[i]
req3 = request.Request(currenturl)
req3.add_header("User-Agent", random.choice(ua_pool))
alldata = request.urlopen(req3).read().decode('utf-8', 'ignore')
pat3 = '<em>共<b>(.*?)</b>'
total_page = re.compile(pat3).findall(alldata)
if len(total_page) > 0:
pages.append(total_page[0])
else:
pass
# print(pages)
for i in range(len(all)):
# num=int(pages[i])
for j in range(1,3):#每个频道有多少页
thispageurl=all[i]+"&page="+str(j)
# print(thispageurl)
yield Request(thispageurl,callback=self.parse)
def parse(self, response):
# 提取频道1,2
pd=response.xpath('//div/div[@class="crumbs-nav-item"]//span[1]/text()').extract()
if len(pd)==0:
pd=['缺失','缺失']
elif len(pd)==1:
pda=pd[0]
pd=[pda,'缺失']
pd1=pd[0]
pd2=pd[1]
# 图书名(60)
bookname=response.xpath('//ul[@class="gl-warp clearfix"]//li[@class="gl-item"]//div/div[@class="p-name"]/a/em/text()').extract()
# 价格和评论数都是通过a的xpath提取出来的
a = response.xpath('//a[@class="p-o-btn focus J_focus"]/@data-sku').extract()
# 价格(60)
etreedata = etree.HTML(response.body)
a = etreedata.xpath('//a[@class="p-o-btn focus J_focus"]/@data-sku')
string = 'https://p.3.cn/prices/mgets?&skuIds='
s = ''
s1 = 'J_' + a[0] + "%"
for i in range(1, len(a)):
s += '2CJ_' + a[i] + "%"
newstring = (string + s1 + s)[:-1]
a = requests.get(newstring,self.headers,verify=False).text
pat = '"p":"(.*?)"'
price = re.compile(pattern=pat).findall(a)
# 评论数(60)
etreedata1 = etree.HTML(response.body)
# https://p.3.cn/prices/mgets?callback=jQuery350868skuIds=J_51499349744%
a = etreedata1.xpath('//a[@class="p-o-btn focus J_focus"]/@data-sku')
com = ''
com1 = 'https://club.jd.com/comment/productCommentSummaries.action?my=pinglun&referenceIds='
for j in range(len(a)):
com += a[j] + ','
commenturl = com1 + com
data = requests.get(commenturl,self.headers,verify=False).text
pat = '"CommentCountStr":"(.*?)",'
commentdata = re.compile(pat).findall(data)
# 作者(60)
author=response.xpath('//span[@class="p-bi-name"]/span[1]/a[1]/text()').extract()
# 出版社(60)
publish= response.xpath('//span[@class="p-bi-store"]/a/text()').extract()
# 链接(60)
link=response.xpath('//li[@class="gl-item"]//div[@class="p-name"]/a/@href').extract()
item=JingdongItem()
for i in range(len(link)):
# print('第一频道:',pd1,'第二频道:',pd2,'d',"书名:",bookname[i],"价格:",price[i],"总共有多少评价数量:",commentdata[i],"图书的作者是:",author[i],"图书的出版社:",publish[i],"图书链接是",link[i])
item['pd1']=pd1
item['pd2']=pd2
item["bookname"]=bookname[i]
item['price']=price[i]
item['commentdata']=commentdata[i]
item['author']=author[i]
item['publish']=publish[i]
item['link']=link[i]
yield item
获取网页对象可以使用两种方法:
(1)使用urllib模块的urlopen方法
from urllib import request
string2='https://channel.jd.com/p_wenxuezongheguan.html'
req2 = request.Request(string2) req2.add_header("User-Agent", random.choice(ua_pool))
alldata = request.urlopen(req2).read().decode('utf-8', 'ignore')
(2)使用requests模块的get/或者post方法:
import requests
res=request.get(url)#获取网页对象
result=res.text()#获取网页源代码
然后自己根据正则或者xpath解析,就可以获取到自己想要的网页内容
代码中的headers作用是使用requests方式访问网页模拟成浏览器
a = requests.get(newstring,headers,verify=False).text(我们只需要在get方法添加headers参数即可)
headers = {
"Accept-Encoding": "gzip, deflate, sdch, br",
"Accept-Language": "zh-CN,zh;q=0.8",
"Connection": "keep-alive",
"Referer:https": "//channel.jd.com/1713-3258.html",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"
}
下面的ua_pool是使用第一种方式模拟浏览器用到的 (第一种方式给的代码提到了如何模拟成浏览器)
ua_pool = [
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1 QQBrowser/6.9.11079.201',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)',
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0'
]
下面的代码是作用是爬取文学综合馆下面51个类别的url
allurl = []
# # 只爬取文学综合馆
# # string1 = result1[1].replace('\/', '/')
#string2 = "https:" + string1
string2='https://channel.jd.com/p_wenxuezongheguan.html'
req2 = request.Request(string2)
req2.add_header("User-Agent", random.choice(ua_pool))
alldata = request.urlopen(req2).read().decode('utf-8', 'ignore')
# print(alldata)
e = etree.HTML(alldata)
zz = e.xpath('//div[@class="ext"]//a/@href')
for i in range(len(zz)):
if zz[i][0] == '/':
currenturl = 'https:' + zz[i]
allurl.append(currenturl)
else:
allurl.append(currenturl)
51个url经过去重之后留下49个url(这里的的url我们称作第二频道,第一频道是我们上面提到的小说、文学、青春文学、动漫、传记)
下面我们以第一频道小说下面的第二频道侦探悬疑为例:
下面的代码是为了获取第二频道有多少页
# 获取每个第二频道有多少页码
pages = []
for i in range(len(all)):
currenturl = all[i]
req3 = request.Request(currenturl)
req3.add_header("User-Agent", random.choice(ua_pool))
alldata = request.urlopen(req3).read().decode('utf-8', 'ignore')
pat3 = '<em>共<b>(.*?)</b>'
total_page = re.compile(pat3).findall(alldata)
if len(total_page) > 0:
pages.append(total_page[0])
我们那里面的xpath解析在原网页看看 <em>共<b>(.*?)</b> 获取总页数是266页
获取总页数的作用是为了实现翻页的作用:例如看看第二页第三页网址
https://list.jd.com/list.html?cat=1713,3258,3304&page=2
https://list.jd.com/list.html?cat=1713,3258,3304&page=3
通过改变page后面的数值我们就可以实现翻页
下面的代码是实现,给定获取给定的第二频道(49个),每个频道有多少页 例如上面的侦探悬疑有266页,我们需要爬取266页
科幻一共267页,我们需要构造出来267个url
for i in range(len(all)):
num=int(pages[i])
for j in range(1,num):#每个频道有多少页
thispageurl=all[i]+"&page="+str(j)
# print(thispageurl)
yield Request(thispageurl,callback=self.parse)
每个频道的url怎么构造(下面两个为例)
侦探悬疑频道
https://list.jd.com/list.html?cat=1713,3258,3304
科幻频道
https://list.jd.com/list.html?cat=1713,3258,33041713,3258,6569
我们发现两个频道的cat=后面的编号不一样,上面获取51频道,获取的就是cat=后面的编号,使用编号,加上我们固定的https://list.jd.com/list.html?cat=字符串,在加上page=编号 ,这样就获取了每个频道下面的所有网友,下面就是使用 parse函数进行解析出来每个页面里面的(60个图书的图书信息(作者,价格,出版社啥的))
3每个页面解析也是重头戏
我们要爬取的几个信息中价格和评论数在原网页中找不到(使用Fiddler4抓包方法能解决)
拿评价数进行举列子
出现数据之后
我们找到关于一部分评价数但不知道是那种商品(好多评价数是以万计算的)
找到好几个涉及到评论数
我们将其网址分别复制出来
我们将上面的网址使用浏览器打开发现就是评论数(我们大概数了一下没有60个(一页的图书大概有至少60个,有的地方有两本))
我们怎么才能构造出来一个上面的url一次获取一页面所有的评论数量了
分析上面的三个url其中有变得地方,也有不变的地方 是https://club.jd.com/comment/productCommentSummaries.action?my=pinglun&referenceIds=,变得地方是后面的数字,
里面一个个数,看起来很诱人,好像代表一本本书,我们去验证一下阿
我们将 data-sku="在源码中找找
我们发现相同的数值重复三次,183/3=61
我们在原网页中找找,发现有个地方出现下面这个心机婊(这样一共有61本书),证明了我们的假设,上面的数字对应的是一本本书籍
我们抓包出来的评价数需要找到对应的书籍构造成url来获取,我们现在的目的就是获取每本书的编号
上面的分析过程就完了下面上代码
通过我们自己构造的url的确能找到61个评论数(自己构造的)
https://club.jd.com/comment/productCommentSummaries.action?(不变)my=pinglun&referenceIds=12430168,12018031,12135337,11452840,12246850,12168081,12342756,12532630,12449731,12298700,11984135,12496717,12579878,12386641,12261987,12614100,12367964,11846850,12397018,12539377,12611240,12354843,12431456,12570998,11585881,12420648,12335303,12511214,11846856,12611238,12569334,12098074,11715315,12099066,12481180,12500210,12146669,12029805,11479404,12528507,12486624,11955909,11720490,11951658,11489347,11577583,12559796,12094982,12102776,11789205,11775154,11920399,12239478,11145344,12192175,12335097,12337727,11932116,12206744,12346167,12354821,(数字变化的)
我们使用pycharm另开了一个窗口,创建 评论数抓包分析.py文件
#-*-coding:utf-8-*-
__author__ = 'fankai'
import requests
from lxml import etree
import re
url='https://list.jd.com/list.html?cat=1713,3258,3304&page=1'
response=requests.get(url,verify=False).text
etreedata=etree.HTML(response)
# https://p.3.cn/prices/mgets?callback=jQuery350868skuIds=J_51499349744%
a=etreedata.xpath('//a[@class="p-o-btn focus J_focus"]/@data-sku')
# print(a)
com=''
com1='https://club.jd.com/comment/productCommentSummaries.action?my=pinglun&referenceIds='
for j in range(len(a)):
com+=a[j]+','
commenturl=com1+com
data=requests.get(commenturl,verify=False).text
pat='"CommentCountStr":"(.*?)",'
commentdata=re.compile(pat).findall(data)
print(commentdata)
print(len(commentdata))
运行结果如下
下面就将这个写的爬虫文件加载到原来创建的scrapy工程里面
(2)其中价格也是相同的原理(和评价数找到的方法一样)
#-*-coding:utf-8-*-
__author__ = 'fankai'
import requests
from lxml import etree
import re
url='https://list.jd.com/list.html?cat=1713,3258,3304&page=1'
response=requests.get(url,verify=False).text
etreedata=etree.HTML(response)
a=etreedata.xpath('//a[@class="p-o-btn focus J_focus"]/@data-sku')
print(a)
string='https://p.3.cn/prices/mgets?&skuIds='
s=''
s1='J_'+a[0]+"%"
for i in range(1,len(a)):
s+='2CJ_'+a[i]+"%"
newstring=(string+s1+s)[:-1]
a=requests.get(newstring,verify=False).text
pat='"p":"(.*?)"'
price=re.compile(pattern=pat).findall(a)
print(price)
print(len(price))
下面直给出运行结果图
将代码加载到scrapy工程里面就行。(自己搞了2天半(回忆爬虫这些用了多半天))
到这里基本上难点都没有了,对了settings设置
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0' # Obey robots.txt rules ROBOTSTXT_OBEY = False
xpath解析基本语法 可以参考下面的(自己多敲敲//div ,//div/div[@class="某个值"]什么的都会了)
我们现用表格列举一下几个常用规则:
表达式描述
nodename选取此节点的所有子节点
/从当前节点选取直接子节点
//从当前节点选取子孙节点
.选取当前节点
..选取当前节点的父节点
@选取属性
https://zhuanlan.zhihu.com/p/29436838