scrapy+selenuim中间件爬取京东图书有详细思考过程(涉及较广适合练手)

本文主要是介绍scrapy+selenuim中间件爬取京东图书有详细思考过程(涉及较广适合练手),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

网上很多版本的爬取京东图书都失效了

现在这个版本是能运行的截至到编辑的日期的前后(往后不敢保证)

gitee仓库网址:https://gitee.com/cc2436686/jd_book_spider   (有详细注释和思考过程)

下面就来看看吧

首先看看我们要爬取的页面

   https://book.jd.com/booksort.html

然后用request直接请求在对返回结果进行关键字匹配

好了接下来就转向目标去按f12抓包去

可以看出这个接口就包含了我们想要的内容接下来只要伪造请求获取数据就行了

直接请求这个接口

那就研究研究这个接口

    https://pjapi.jd.com/book/sort?source=bookSort&callback=jsonp_1606557102964_82922

 

经过测试可以的出除了数字以外其他都是固定参数

   而且中间一大串数字一看就是时间戳

  我们可以用python来模拟

import requests
import time#因为要爬取的项目是从这个https://book.jd.com/booksort.html主页面开始的,
# 因为大分类的小说和小分类的中国当代小说包括表示其种类的id都是通过请求接口来渲染
# 所以直接通过请求页面是获取不到相应的数据的所以只能破解下面这个接口
# https://pjapi.jd.com/book/sort?source=bookSort&callback=jsonp_1606487589792_60024
# 直接通过浏览器控制台中的network就能抓到这个包
# 经过测试source后面的参数是不变的
# callback后面的参数是jsonp_加时间戳_随机的四到五位数字
# 下面就用python自带的time软件生成时间戳来进行请求
# 再加上referer参数https://book.jd.com/ 伪装成官网跳转过来就ok了
t=int(time.time()* 1000)url = "https://pjapi.jd.com/book/sort?source=bookSort"
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' ,'callback':'jsonp_'+str(t)+'_60005','referer':'https://book.jd.com/'
}
response2 = requests.get(url, headers=headers)
print(response2.request.headers)
print(response2.content.decode())

 

ok接口数据获取成功接下来就去提取页面中的url

 

 

从上面两张图片可以看出分类链接的地址是由大分类小说的fathercategoryid和大分类小说的categoryid和子节点的categoryid拼接而成的

也就是说我们可以直接通过这个接口来构造所有的请求

class BookSpider (scrapy.Spider):name = 'book'# 修改允许的域allowed_domains = ['jd.com']# 修改起始的urlstart_urls = ['https://pjapi.jd.com/book/sort?source=bookSort']# 起始的京东所有图书分类是通过ajax动态加载的所以在页面上搜寻不到所以只能通过# 请求接口来获取所有的图书分类信息和对应的编号用于下面的请求拼接# 此请求接口直接输入接口地址或者去用页面当时加载的页面接口参数去请求是会被认为# 是非法请求如果想要知道如何请求接口请看JD下面的text.py去看相应的伪装过程# 当然也可以去pipelines.py里去看当然没text.py里详细def parse(self, response):# print(response.body.decode())#如果请求成功返回就是json格式用json模块解析big_dict = json.loads (response.body.decode ())for big_node in big_dict['data']:  # 对返回内容中的data进行遍历for small_node in big_node['sonList']:item = JdItem ()item['big_category'] = big_node['categoryName']  # 获取data中大分类的名字赋值给item# 因为大分类的链接点进去之后可以看出是https://channel.jd.com/1713-3258.html格式的可以看出# 是由大分类中的fatherCategoryId: 1713和大分类本身的id拼接而成因为我爬的是图书所以大分类中的# 的fatherCategoryId一直不会变所以图省事直接1713写死了后面再加上大分类本身的id就完成了item['big_category_link'] = 'https://channel.jd.com/1713-' + str (int (big_node['categoryId'])) + '.html'item['small_category'] = small_node['categoryName']# 再找到大分类中的小分类的名字赋值给small_categoryitem['small_category_link'] = "https://list.jd.com/list.html?cat=1713," + str (int (big_node['categoryId'])) + "," + str (int (small_node['categoryId'])) + '&page=1'# 小分类的链接就是大分类的father的id加上小分类的id再加上图书本身的id就行了# 如果不加&page=1拼接之后https://list.jd.com/list.html?cat=1713,3258,3297&page=1请求的默认也是第一页# 因为下面的翻页需要这里还是添加上page=1这个参数# https://list.jd.com/list.html?cat=1713,3258,3297&page=1 小分类的完整链接(样例)# 再为所有的小分类链接创建请求并且跳转到parse_detail这个解析函数再用meta将详细item项目传输过去yield scrapy.Request (url=item['small_category_link'],callback=self.parse_detail,meta={'item': item})

 

构造好所有的小分类的请求url后接下来就是爬取对应的详情页面的图书信息了

我们要爬取的目标

import scrapyclass JdItem(scrapy.Item):#爬虫要抓取的内容如下big_category = scrapy.Field()#图书大分类的名字big_category_link = scrapy.Field()#图书大分类的链接(好像没啥用0.0)small_category = scrapy.Field()#图书小分类的名字small_category_link = scrapy.Field()#图书小分类的链接bookname=scrapy.Field()#图书的名字author=scrapy.Field()#作者的名字(有些图书可能没有作者默认就是None)link =scrapy.Field()#图书的详情页的链接price=scrapy.Field()#图书的价格pass

这些基本的信息提取就不细讲了后面有代码要解决的就是翻页问题因为你查看翻页的按钮信息会发现

又是通过js动态加载的

本着能用scrapy就不用selenuim的原则

继续分析

可以看到前面onclick时间中的参数却透露出信息了

通过翻页前后

第一页

https://list.jd.com/list.html?cat=1713,3258,3297

或者

https://list.jd.com/list.html?cat=1713,3258,3297&page=1

第二页

https://list.jd.com/list.html?cat=1713%2C3258%2C3297&page=3&s=57&click=0

的对比可以发现

page参数是翻页的关键其他代码可以忽略

也就是说只要修改page的信息就可以完成

page是以+=2递增来翻页的

所以我们可以不去提取翻页url(当然也提取不到)直接构造翻页请求

但是如何判断是否是最后一页呢?

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

(我没注意到这个页面图书信息的加载是懒加载

又快马加鞭地修改。。。。。。。。。。。。。。。。。。。。。

好了本人学识浅薄只能用selenuim作为中间件完成了图书的懒加载

中途又遇到频繁请求被重定向的问题导致分支被断

我试了试判断是否被重定向再吃重复发起请求也解决了这个问题

我单独放了一个小分类进去成功完成了一个小分支的所有图书的全部抓取)

好了当无事发生继续分析

当我门翻页后有且只有30个标签也就是说只有30个图书信息但是当我们往下拉的时候

触发了懒加载才能继续加载数据

数量刚好也就是60个所以我们可以使用selenuim作为中间件来触发懒加载

再判断li的个数如果为0就是被重定向了,因为最后一页不可能一本书都没

逻辑如下

        if len (books_list) == 0:print ("貌似被重定向")print("重新发起请求")yield scrapy.Request (url=response.url,callback=self.parse_detail,dont_filter=True,meta={'item': response.meta['item'],'dont_redirect': True})if len (books_list) == 60:# https://list.jd.com/list.html?cat=1713,3258,3297&page=1 #例子url就是这样下面就是对url中的page进行累加操作最后再拼接list = re.split ("&", response.url)# 通过&分隔urlstr1 = str (int (re.findall ('\d+', list[1])[0]) + 2)# 提取第二个page中的数字再将其加2后转为字符串next_url = list[0] + '&page=' + str1# 拼接urlprint ("拼接下一页url", next_url)# 为url创建request请求将meta中的item对象传入# (如果不传入item对象翻页后会因为response取不到item对象而报错而不会去执行下面的代码)yield scrapy.Request (url=next_url,callback=self.parse_detail,dont_filter=True,meta={'item': response.meta['item'],'dont_redirect': True})

因为我是临时加入的selenuim所以只是单纯地作为中间件来使用

大家可能在翻页的时候会遇到

https://list.jd.com/list.html?cat=1713%2C3258%2C3297&page=135&s=4021&click=0

类似的url看到这个cat后面的参数再看看之前cat后面的参数就知道被加密了

百度搜索url解密直接丢进去就能看到

https://list.jd.com/list.html?cat=1713,3258,3297&page=135&s=4021&click=0

跟我们之前id拼接一模一样所以不必担心

直接传我们通过id拼接的url即可

接下来就展示自己的一些关键代码如果想要全部的项目就去gitee仓库那边去拷贝

gitee仓库网址:https://gitee.com/cc2436686/jd_book_spider   (有详细注释和思考过程)

爬虫核心代码book

import timeimport scrapyimport jsonfrom JD.items import JdItemimport reclass BookSpider (scrapy.Spider):name = 'book'# 修改允许的域allowed_domains = ['jd.com']# 修改起始的urlstart_urls = ['https://pjapi.jd.com/book/sort?source=bookSort']# 起始的京东所有图书分类是通过ajax动态加载的所以在页面上搜寻不到所以只能通过# 请求接口来获取所有的图书分类信息和对应的编号用于下面的请求拼接# 此请求接口直接输入接口地址或者去用页面当时加载的页面接口参数去请求是会被认为# 是非法请求如果想要知道如何请求接口请看JD下面的text.py去看相应的伪装过程# 当然也可以去pipelines.py里去看当然没text.py里详细def parse(self, response):# print(response.body.decode())#如果请求成功返回就是json格式用json模块解析big_dict = json.loads (response.body.decode ())for big_node in big_dict['data']:  # 对返回内容中的data进行遍历for small_node in big_node['sonList']:item = JdItem ()item['big_category'] = big_node['categoryName']  # 获取data中大分类的名字赋值给item# 因为大分类的链接点进去之后可以看出是https://channel.jd.com/1713-3258.html格式的可以看出# 是由大分类中的fatherCategoryId: 1713和大分类本身的id拼接而成因为我爬的是图书所以大分类中的# 的fatherCategoryId一直不会变所以图省事直接1713写死了后面再加上大分类本身的id就完成了item['big_category_link'] = 'https://channel.jd.com/1713-' + str (int (big_node['categoryId'])) + '.html'item['small_category'] = small_node['categoryName']# 再找到大分类中的小分类的名字赋值给small_categoryitem['small_category_link'] = "https://list.jd.com/list.html?cat=1713," + str (int (big_node['categoryId'])) + "," + str (int (small_node['categoryId'])) + '&page=1'# 小分类的链接就是大分类的father的id加上小分类的id再加上图书本身的id就行了# 如果不加&page=1拼接之后https://list.jd.com/list.html?cat=1713,3258,3297&page=1请求的默认也是第一页# 因为下面的翻页需要这里还是添加上page=1这个参数# https://list.jd.com/list.html?cat=1713,3258,3297&page=1 小分类的完整链接(样例)# 再为所有的小分类链接创建请求并且跳转到parse_detail这个解析函数再用meta将详细item项目传输过去yield scrapy.Request (# url=item['small_category_link'],url=item['small_category_link'],callback=self.parse_detail,meta={'item': item,'dont_redirect': True})def parse_detail(self, response):# 接收到parse传过来的item信息# 里面包含着大小分类的名字和链接item = response.meta['item']books_list = response.xpath ("//*[@id='J_goodsList']/ul/li")# 查看小分类链接里所有的图书信息都是由ul下面的li标签排列而成的所以就先获取所有的li标签# 通过查看页面后得知翻页是没有对应的url是通过js动态加载的# 但是通过对于翻页前后网址的对比可以看出从翻页操作是通过page+=2完成的# 也就是说只要改变page的值就能实现翻页效果不需要提取url(也没有url可以提取)# 下面就是判断当前页面中的图书也就是目标li的个数是不是60个# 泪崩。。图书是懒加载的得通过selenuim作为中间件渲染页面# 如果是尾页的话就不满30个就会跳过这个判断不去创建请求if len (books_list) == 0:print ("貌似被重定向")time.sleep(3)print("3秒后重新发起请求")yield scrapy.Request (url=response.url,callback=self.parse_detail,dont_filter=True,meta={'item': response.meta['item'],'dont_redirect': True})if len (books_list) == 60:# https://list.jd.com/list.html?cat=1713,3258,3297&page=1 #例子url就是这样下面就是对url中的page进行累加操作最后再拼接list = re.split ("&", response.url)# 通过&分隔urlstr1 = str (int (re.findall ('\d+', list[1])[0]) + 2)# 提取第二个page中的数字再将其加2后转为字符串next_url = list[0] + '&page=' + str1# 拼接urlprint ("拼接下一页url", next_url)# 为url创建request请求将meta中的item对象传入# (如果不传入item对象翻页后会因为response取不到item对象而报错而不会去执行下面的代码)yield scrapy.Request (url=next_url,callback=self.parse_detail,dont_filter=True,meta={'item': response.meta['item'],'dont_redirect': True})for book in books_list:# 下面就是开心的获取信息item['small_category_link'] = response.url# 这里要重置一下小分类链接因为翻页的缘故所以page的信息有所改变item['link'] = response.urljoin (book.xpath ("./div/div[@class='p-img']/a/@href").extract_first ())# 提取到每个图书的详情页链接item['bookname'] = book.xpath ("./div/div[@class='p-name']/a/em/text()").extract_first ()# 提取到每个图书的书名item['author'] = book.xpath ("./div/div[@class='p-bookdetails']/span[@class='p-bi-name']/a/text()").extract_first ()# 提取到每个图书的作者item['price'] = book.xpath ("./div/div[@class='p-price']/strong/i/text()").extract_first ()# 提取到每个图书的价格(跟之前比起来价格不是通过接口请求的而是直接加载到页面的省了一些功夫)yield item# 返回图书的信息

 

下载中间件

from scrapy.http import HtmlResponsefrom selenium import webdriverimport timeclass JdSpiderMiddleware:passclass JdDownloaderMiddleware:# Not all methods need to be defined. If a method is not defined,# scrapy acts as if the downloader middleware does not modify the# passed objects.def __init__(self):opt = webdriver.ChromeOptions ()# opt.add_argument ('--headless')  # 如果想看到具体的翻页情况(就注释)可以打开浏览器看(蛮有趣的)# 建议用有界面的比较好因为可以看到运行情况opt.add_argument ('user-agent="Mozilla/5.0 (iPod; U; CPU iPhone OS 2_1 like Mac OS X; ja-jp) ''AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5F137 Safari/525.20"')opt.add_argument ('https://book.jd.com/booksort.html')self.driver = webdriver.Chrome (options=opt)# 其他都不重要只用看下面这个方法就够了def process_request(self, request, spider):if 'page' in request.url:# 直接跳转小分类链接是会出错的必须要加上referer模拟从https://book.jd.com/booksort.html页面点击过来self.driver.get (request.url)self.driver.execute_script ('window.scrollTo(0,document.body.scrollHeight)')time.sleep (2)html = self.driver.page_sourceself.preurl = request.urlreturn HtmlResponse (url=request.url, body=html.encode ())if "sort?source=bookSort" in request.url:# 判断请求的是不是请求大分类和小分类图书的详细信息的接口网址# 如果是的话就模拟时间戳凭借urlprint ("请求接口数据")t = int (time.time () * 1000)request.headers['callback'] = 'jsonp_{0}_2175'.format (t)# 这里需要添加从https://book.jd.com/的referer信息request.headers['referer'] = 'https://book.jd.com/'return Nonedef close(self, spider):self.driver.quit ()

管道(一定要输入自己的mongodb数据库的ip和端口,还有登陆验证才行)

from pymongo import MongoClientclass JdPipeline:def __init__(self):self.number=0# 创建数据库链接对象#只要创建好mongodb的客户端和登录操作就可以直接插入数据了client = MongoClient('您的mongodb数据库地址',27017)#输入mongodb的ip地址和端口号self.db = client['admin']# 选择admin数据库self.db.authenticate('您的用户名','您的密码')#进行用户验证def process_item(self, item, spider):Item=dict(item)self.db.book.insert(Item)#往book集合里插入数据self.number+=1# print(Item)#书的详细信息print('第',self.number,'本书存入成功')return item

setting(只是配置了下载中间件和管道)

BOT_NAME = 'JD'SPIDER_MODULES = ['JD.spiders']
NEWSPIDER_MODULE = 'JD.spiders'
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
LOG_LEVEL = "WARNING" 
#如果想看详细信息可以吧LOG_LEVEL改成debug
ROBOTSTXT_OBEY = False
REDIRECT_ENABLED = FalseDOWNLOADER_MIDDLEWARES = {'JD.middlewares.JdDownloaderMiddleware': 543,
}ITEM_PIPELINES = {'JD.pipelines.JdPipeline': 300,
}

items文件

import scrapyclass JdItem(scrapy.Item):#爬虫要抓取的内容如下big_category = scrapy.Field()#图书大分类的名字big_category_link = scrapy.Field()#图书大分类的链接(好像没啥用0.0)small_category = scrapy.Field()#图书小分类的名字small_category_link = scrapy.Field()#图书小分类的链接bookname=scrapy.Field()#图书的名字author=scrapy.Field()#作者的名字(有些图书可能没有作者默认就是None)link =scrapy.Field()#图书的详情页的链接price=scrapy.Field()#图书的价格pass

差不多就这些了再捞一下自己的gitee项目仓库地址

gitee仓库网址:https://gitee.com/cc2436686/jd_book_spider   (有详细注释和思考过程)

效率也就这样吧初步估计有2600万条数据

必须得用分布式爬虫才能爬完

之后会发分布式爬虫版京东图书爬虫

这篇关于scrapy+selenuim中间件爬取京东图书有详细思考过程(涉及较广适合练手)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/902651

相关文章

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

PyInstaller打包selenium-wire过程中常见问题和解决指南

《PyInstaller打包selenium-wire过程中常见问题和解决指南》常用的打包工具PyInstaller能将Python项目打包成单个可执行文件,但也会因为兼容性问题和路径管理而出现各种运... 目录前言1. 背景2. 可能遇到的问题概述3. PyInstaller 打包步骤及参数配置4. 依赖

python连接本地SQL server详细图文教程

《python连接本地SQLserver详细图文教程》在数据分析领域,经常需要从数据库中获取数据进行分析和处理,下面:本文主要介绍python连接本地SQLserver的相关资料,文中通过代码... 目录一.设置本地账号1.新建用户2.开启双重验证3,开启TCP/IP本地服务二js.python连接实例1.

Nginx中配置HTTP/2协议的详细指南

《Nginx中配置HTTP/2协议的详细指南》HTTP/2是HTTP协议的下一代版本,旨在提高性能、减少延迟并优化现代网络环境中的通信效率,本文将为大家介绍Nginx配置HTTP/2协议想详细步骤,需... 目录一、HTTP/2 协议概述1.HTTP/22. HTTP/2 的核心特性3. HTTP/2 的优

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

Python基础文件操作方法超详细讲解(详解版)

《Python基础文件操作方法超详细讲解(详解版)》文件就是操作系统为用户或应用程序提供的一个读写硬盘的虚拟单位,文件的核心操作就是读和写,:本文主要介绍Python基础文件操作方法超详细讲解的相... 目录一、文件操作1. 文件打开与关闭1.1 打开文件1.2 关闭文件2. 访问模式及说明二、文件读写1.

Ubuntu中远程连接Mysql数据库的详细图文教程

《Ubuntu中远程连接Mysql数据库的详细图文教程》Ubuntu是一个以桌面应用为主的Linux发行版操作系统,这篇文章主要为大家详细介绍了Ubuntu中远程连接Mysql数据库的详细图文教程,有... 目录1、版本2、检查有没有mysql2.1 查询是否安装了Mysql包2.2 查看Mysql版本2.

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数