最近有在做小学期的项目,用scrapy实现爬取图书,下面是我实现的过程。
具体实现功能有:二级页面带自动翻页功能,三级页面的第一页爬取,大小类别的区分。
框架:scrapy
使用到chrome的插件:Selenium
插件的链接:chromediver提权码:5n0l。解压后将他丢入C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe(这是我的路径可以参考下)
数据库:mongoDB
三级页面请看我另外一篇文章:https://blog.csdn.net/weixin_43094046/article/details/107933101
二级页面带自动翻页功能
首先需要在对应的页面下创建scrapy项目这里我们选择创建两个一个叫myProject用于测试xpath因为后期一旦启动项目就开始爬取费时,一个叫suningBook具体实现二级页面带自动翻页功能。
目录:
废话不多说,直接开淦。
首先选取爬取的页面这里我选择的是小说部分链接:苏宁图书小说
分析页面定位到一整本书
然后下面的价格,书名,店铺,评价人数,链接(作为我们跳转的依据)是需要爬取的,这里就不考虑图片了。二级页面中的作者,出版社,出版时间。
在items中定义
import scrapy
class SuningbookItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
id=scrapy.Field()#出版时间
bkName=scrapy.Field()#图书的名字
price=scrapy.Field()#图书的价格
commenrNumber=scrapy.Field()#评价的人数
bkShop=scrapy.Field()#所在的书店
bkUrl=scrapy.Field()#URL
author=scrapy.Field()#作者
press=scrapy.Field()#出版社
publishTime=scrapy.Field()#出版时间
在爬取的时候没有考虑数据类型全是字符串,后期在做处理。
写具体爬虫方法
from copy import deepcopy #这里是考虑了数据丢失问题,不然二级页面中数据无法获取
import scrapy
class SuningspiderSpider(scrapy.Spider):
name = 'suningSpider'
allowed_domains = ['suning.com']
start_urls = ['https://list.suning.com/1-502320-0.html']
def parse(self, response):
book_lists = response.xpath('//div[@id="filter-results"]/ul/li')
print(len(book_lists))
item = {}
count = 0
for book in book_lists:#遍历图书
item['id'] = count+1 #做一个计数器
item['bkName'] = book.xpath('.//div[@class="res-info"]/p[2]/a/text()').extract_first()
price1 = str(book.xpath('.//div[@class = "res-info"]/p[1]/em/text()').extract_first())
price2 = str(
book.xpath('//*[@id="filter-results"]/ul/li[1]/div/div/div/div[2]/p[1]/em/i[1]/text()').extract_first())
item["price"] = price1 + price2 #价格拼接
item['commentNumber'] = book.xpath('.//div[@class="res-info"]/p[3]/a[1]/text()').extract_first()
item['bkShop'] = book.xpath('.//div[@class="res-info"]/p[4]/a[1]/text()').extract_first()
item['bkUrl'] = "https:" + book.xpath('.//div[@class="res-info"]/p[2]/a[1]/@href').extract_first()
yield scrapy.Request(item["bkUrl"], callback=self.parse_detail, meta={"item": deepcopy(item)}) #根据所获取到的url做跳转
count = count + 1
def parse_detail(self, response):
item = response.meta["item"]
item["author"] = response.xpath('//li[@class="pb-item"][1]/text()').extract_first() if response.xpath('//li[@class="pb-item"][1]/text()').extract_first() is not None else "未知"
item["press"] = response.xpath('//li[@class="pb-item"][2]/text()').extract_first() if response.xpath('//li[@class="pb-item"][2]/text()').extract_first() is not None else "未知"
item["publishTime"] = response.xpath('//li[@class="pb-item"][3]/span[2]/text()').extract_first() if response.xpath('//li[@class="pb-item"][3]/text()').extract_first() is not None else "未知"
yield item
print(item)
这里的xpath都是通过myProject测试后填写
接下来我们需要翻页所有就使用到了Selenium上文有链接。在middleware中写对应使用的方法,这里我只考虑翻三页。
class SeleniumDownloaderMiddleware:
"""
构造方法
"""
def __init__(self):
self.browser = webdriver.Chrome(r'C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe') # 每个spider初始化一次driver,自己的Chromedriver路径
"""
动态网页处理中间件
"""
def process_request(self, request, spider):#普通selenium的使用
if spider.name == 'suningSpider'and not (request.url.startswith("https://product")):
# 根据特定Spider 决定是否利用selenium模拟浏览器爬取
self.browser.get(request.url) # selenium请求了网页 获得的Response 因此process_request不需要再返回Request给Downloader
time.sleep(5) # 延时5s 待网页完全加载后打印html文本
print("访问:", request.url)
print("current_url", self.browser.current_url)
urlchangecnt = 0
body = ""
while (urlchangecnt < 3) :
self.browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')#这里下滑是因为进入页面只有30本书,要想得到整个页面的图书就需要下滑到底端动作,则数据是60本书
time.sleep(5)#等待页面加载
body = body + self.browser.page_source
self.browser.find_element_by_id('nextPage').send_keys(Keys.ENTER)
urlchangecnt = urlchangecnt + 1
return HtmlResponse(self.browser.current_url, body=body,
encoding="utf-8", request=request) # 这里必须返回Response的类或派生类
#只有这样Scrapy才不会返回Request给Downloader
pipelines中去填写导入数据库的方法,以及数据的清洗
import pymongo
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
class SuningbookPipeline:
def __init__(self):
host=settings['MONGODB_HOST']
port=settings['MONGODB_PORT']
db_name=settings['MONGODB_DBNAME']
client=pymongo.MongoClient(host=host,port=port)
db=client[db_name]
self.post=db[settings['MONGODB_DOCNAME']]
def process_item(self, item, spider):
item["bkName"]=item["bkName"].strip()
item["commentNumber"]=item["commentNumber"].strip('+')
item["author"]=item["author"].strip()
item["press"]=item["press"].strip()
book_info=dict(item)
self.post.insert(book_info)
return item
重要的是在settings里面进行整个项目的配置
BOT_NAME = 'suningBook'
SPIDER_MODULES = ['suningBook.spiders']
NEWSPIDER_MODULE = 'suningBook.spiders'
MONGODB_HOST='127.0.0.1' #数据库地址
MONGODB_PORT=27017
MONGODB_DBNAME='suning' #数据库名
MONGODB_DOCNAME='book_info' #集合的名字
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'suningBook (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False #
LOG_LEVEL = 'WARNING' #为了使得控制台输出整洁调整了输出的等级,并且报错会在本地生产log。txt的文件
LOG_FILE = 'log.txt'
CONCURRENT_REQUESTS = 100
COOKIES_ENABLED = False
RETRY_ENABLED = False
DOWNLOAD_TIMEOUT = 3
#下端找到Middleware,pipelins的配置(默认是注释了的),将插件的优先度调整到最高
DOWNLOADER_MIDDLEWARES = {
'suningBook.middlewares.SuningbookDownloaderMiddleware': 543,
'suningBook.middlewares.SeleniumDownloaderMiddleware': 1,
}
ITEM_PIPELINES = {
'suningBook.pipelines.SuningbookPipeline': 300,
}
#这里是做数据输出的缓冲,就不需要在Middleware中数据的睡眠了,使得数据输出完整。
AUTOTHROTTLE_ENABLED = True
遇到的问题:
- 数据丢失
- 数据爬取过慢
- 无法翻页
解决方法:
-
settings中设置AUTOTHROTTLE_ENABLED = True以及爬虫中的decopy还有我在获取数据中发现数据库中一直都是177条,三页数据本该180条,我就在suningSpider里面第一个函数做item条数输出,发现是180条则我就想是不是这里传给pip出问题还是pip传给数据库出现问题,则我又在pip中打印item。发现数据少了三条就确定了是suningSpider中yield传递的时候出现问题。然后我就在控制台一个id一个ID找发现这个9号并没有在控制台输出,具体到页面上去看发现他的作者和出版时间没有,这样就导致了我的数据丢失,所以就在函数中做了个判断,如果没有的话就填写“未知”,这样的我数据180条就正确了。下面是有问题的图书,只有一个li标签。
-
在SeleniumDownloaderMiddleware中写了一个判断并不需要具体的每一本输还有进行跳转然后睡眠,只需要获取数据就行and not (request.url.startswith(“https://product”))。以及在settings中CONCURRENT_REQUESTS = 100,并行的数据。
-
老师提供的是定位然后点击.click()下一页按钮,发现总是不能,于是我就在网上搜搜.send_keys(Keys.ENTER)发现这个方法模拟键盘回车,能够成功。为了弄清楚,就又尝试了下,发现这个cilck的id定位确实有点问题,用xpath来就行。
以下是数据库截图
整个项目:https://pan.baidu.com/s/1hOR4xJd8JLA_seu9ZwNz8A
提取码:e0zd