python scrapy结合selenium爬取JD数据
JD的数据是js动态加载的需要selenium模拟鼠标动作向后滑动才加载完成,但是单纯的用selenium又很慢,所以用selenium和scrapy框架结合一下,会快一些。
第一步:创建scrapy文件
scrapy startproject JDpa
cd JDpa
scrapy genspider JD
打开 JD.py
分析jd页面数据
这里我查找的是jd关于python爬虫的相关信息
可以看到所有的信息都在ul
标签下的li
标签列表里面所以
li_list = response.xpath('//*[@id="J_goodsList"]/ul/li')
就可以拿到所有的li
标签
遍历li_list
拿到所有我要的内容
价格:在li
标签下的div[@class="p-price"]
下的strong
标签下的i
标签里面以文本形式储存所以
# 因为div[@class="p-price"]不是直属li标签,中间隔着一个div标签所以是 './/',如果直属的话就是'./'
price = li.xpath('.//div[@class="p-price"]/strong/i/text()')[0]
出版方:在li标签下的div[@class=“p-shopnum”]下的a标签的title属性值所以
shop = li.xpath('.//div[@class="p-shopnum"]/a/@title')[0]
标题,评价都是和前面一样就不多做赘述。
# 标题在em标签中但是又被分割成了几个font标签所以要用'//text()'才能拿到完整的文本信息
# ''.join()方法把name转换成字符串形式
# '//text()'拿到的文本信息会有多余的空格和和换行符 用'.strip()'方法去掉两侧多余的空格和和换行符
name = li.xpath('.//div[@class="p-name"]/a/em//text()')
name = ''.join(name).strip()
commit = li.xpath('.//div[@class="p-commit"]/strong/a/text()')[0]
这样一页数据就都获取了,接下来分析,页面直接URL的规律。前面“https://search.jd.com/Search?keyword=python%E7%88%AC%E8%99%AB&suggest=1.his.0.0
&wq=python%E7%88%AC%E8%99%AB” 这一段都是一样的
&page=1&s=1&click=0
&page=3&s=58&click=0
&page=5&s=121&click=0
&page=7&s=176&click=0
可以看出page=后面是按照n从o开始,2n+1的方式排列的,而s=后面的数字没什么规律,后来我把&s=1&click=0这一段删除后,依然可以正常请求到页面信息,到这里页面URL的规律就找出来了。
一共有100页,所有的url可以表达为:
for i in range(100):
url = 'https://search.jd.com/Search?keyword=python%E7%88%AC%E8%99%AB&suggest=1.his.0.0
&wq=python%E7%88%AC%E8%99%AB&page='+str(i*2+1)
第二步:
- 在爬虫文件中实例化浏览器对象
def __init__(self):
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
self.bro = webdriver.Chrome(options=option)
- 下载中间键拦截所有的url请求,调用selenium请求,然后返回用selenium请求返回的响应对象。
点开middlewares.py
可以把不用的都删掉,只用
def process_response(self, request, response, spider):
# 创建浏览器实例化对象在爬虫文件中,用spider.bro调用
bro = spider.bro
# requst.url 就是拦截到的爬虫文件发起的url
bro.get(request.url)
# selenium模拟鼠标下拉
for i in range(25):
time.sleep(0.3)
bro.execute_script('window.scrollBy(0,300)', '')
time.sleep(3)
page = bro.page_source
# 利用HtmlResponse()实例化一个新的响应对象
n_response = HtmlResponse(url=request.url, body=page, encoding='utf-8', request=request)
# 返回新的响应对象
return n_response
第三步:
- 打开items.py文件,将解析到的数据定义为item类型对象的属性
class JdpaItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field()
price = scrapy.Field()
commit = scrapy.Field()
shop = scrapy.Field()
- 打开pipelines.py文件,接收item封装的数据并做持久化存储(写入本地xls表格)
from openpyxl import Workbook
class JdpaPipeline:
def __init__(self):
self.wb = Workbook()
self.ws = self.wb.active
self.ws.append(['书名', '价格', '评价', '出版方'])
def process_item(self, item, spider):
line = [item['name'], item['price'], item['commit'], item['shop']]
self.ws.append(line)
self.wb.save('./JD python爬虫相关书籍.xls')
return item
3.打开setting.py文件
# 关闭robots协议
ROBOTSTXT_OBEY = False
# 输出日志设为之输出发生错误的日志信息
LOG_LEVEL = 'ERROR'
# 开启下载中键件
DOWNLOADER_MIDDLEWARES = {
'JDpa.middlewares.JdpaDownloaderMiddleware': 543,
}
# 开启管道
ITEM_PIPELINES = {
'JDpa.pipelines.JdpaPipeline': 300,
}
爬虫文件(JD.py)的完整代码:
这里解析shop
的数据按照我之前第一步分析的那样写是有错误的,这个我写完运行之后就出门了,等我回来发现,每页60条数据,一共1000页,我这里只拿到了5800多条数据,而且输出日志有报错list index out of range
,指出是shop =
li.xpath('.//div[@class="p-shopnum"]/a/@title')[0]
这一行,后来找了好久才发现有的是京东自营的,页面shop
这一数据的位置就不对了,就会出现定位不到的情况。我这里改正了用try...except
做异常处理,把定位不到的shop
就直接等于’京东自营‘,后又测试了十几页,是正常的了。
附上京东自营的截图,可以看到根本就没有a
标签,所以定位不到
import scrapy
from selenium import webdriver
from selenium.webdriver import ChromeOptions
from JDpa.items import JdpaItem
class JdSpider(scrapy.Spider):
name = 'JD'
#allowed_domains = ['www.xxx.com']
start_urls = ['https://search.jd.com/Search?keyword=python%E7%88%AC%E8%99%AB&suggest=1.his.0.0&wq=python%E7%88%AC%E8%99%AB&page='+str(i*2+1) for i in range(100)]
def __init__(self):
# 实例化浏览器对象
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
self.bro = webdriver.Chrome(options=option)
def parse(self, response):
item = JdpaItem()
li_list = response.xpath('//*[@id="J_goodsList"]/ul/li')
for li in li_list:
name = li.xpath('.//div[@class="p-name"]/a/em//text()').extract()
name = ''.join(name).strip()
price = li.xpath('.//div[@class="p-price"]/strong/i/text()')[0].extract()
commit = li.xpath('.//div[@class="p-commit"]/strong/a/text()')[0].extract()
try:
shop = li.xpath('.//div[@class="p-shopnum"]/a[1]/@title')[0].extract()
except:
shop = '京东自营'
item['name'] = name
item['price'] = price
item['commit'] = commit
item['shop'] = shop
yield item
# 关闭浏览器
def closed(self, spider):
self.bro.close()
items.py完整代码:
import scrapy
class JdpaItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field()
price = scrapy.Field()
commit = scrapy.Field()
shop = scrapy.Field()
middlewares.py 完整代码:
from scrapy.http import HtmlResponse
from time import sleep
class JdpaDownloaderMiddleware:
def process_response(self, request, response, spider):
bro = spider.bro
bro.get(request.url)
for i in range(25):
sleep(0.3)
bro.execute_script('window.scrollBy(0,300)', '')
sleep(3)
page = bro.page_source
n_response = HtmlResponse(url=request.url, body=page, encoding='utf-8', request=request)
return n_response
pipelines.py 完整代码:
from openpyxl import Workbook
class JdpaPipeline:
def __init__(self):
self.wb = Workbook()
self.ws = self.wb.active
self.ws.append(['书名', '价格', '评价', '出版方'])
def process_item(self, item, spider):
line = [item['name'], item['price'], item['commit'], item['shop']]
self.ws.append(line)
self.wb.save('./JD2 python爬虫相关书籍资料.xls')
return item
到这里就结束了!!!