python scrapy结合selenium爬取JD数据

JD的数据是js动态加载的需要selenium模拟鼠标动作向后滑动才加载完成,但是单纯的用selenium又很慢,所以用selenium和scrapy框架结合一下,会快一些。

第一步:创建scrapy文件

scrapy startproject JDpa
cd JDpa
scrapy genspider JD

打开 JD.py

分析jd页面数据

这里我查找的是jd关于python爬虫的相关信息

python 爬取 js脚本源码 python爬取jsp网页_ide


可以看到所有的信息都在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]

python 爬取 js脚本源码 python爬取jsp网页_selenium_02


出版方:在li标签下的div[@class=“p-shopnum”]下的a标签的title属性值所以

shop = li.xpath('.//div[@class="p-shopnum"]/a/@title')[0]

python 爬取 js脚本源码 python爬取jsp网页_ide_03


标题,评价都是和前面一样就不多做赘述。

# 标题在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]

python 爬取 js脚本源码 python爬取jsp网页_xpath_04

python 爬取 js脚本源码 python爬取jsp网页_selenium_05


这样一页数据就都获取了,接下来分析,页面直接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的规律就找出来了。

python 爬取 js脚本源码 python爬取jsp网页_python_06


一共有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)

第二步:

  1. 在爬虫文件中实例化浏览器对象
def __init__(self):
        option = ChromeOptions()
        option.add_experimental_option('excludeSwitches', ['enable-automation'])
        self.bro = webdriver.Chrome(options=option)
  1. 下载中间键拦截所有的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

第三步:

  1. 打开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()
  1. 打开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标签,所以定位不到

python 爬取 js脚本源码 python爬取jsp网页_selenium_07

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

到这里就结束了!!!

python 爬取 js脚本源码 python爬取jsp网页_xpath_08

python 爬取 js脚本源码 python爬取jsp网页_xpath_09