最近有在做小学期的项目,用scrapy实现爬取图书,下面是我实现的过程。
具体实现功能有:二级页面带自动翻页功能,三级页面的第一页爬取,大小类别的区分。
框架:scrapy
使用到chrome的插件:Selenium
插件的链接:chromediver提权码:5n0l。解压后将他丢入C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe(这是我的路径可以参考下)
数据库:mongoDB
三级页面请看我另外一篇文章:/weixin_43094046/article/details/107933101
二级页面带自动翻页功能
首先需要在对应的页面下创建scrapy项目这里我们选择创建两个一个叫myProject用于测试xpath因为后期一旦启动项目就开始爬取费时,一个叫suningBook具体实现二级页面带自动翻页功能。
目录:
废话不多说,直接开淦。
首先选取爬取的页面这里我选择的是小说部分链接:苏宁图书小说
分析页面定位到一整本书
然后下面的价格,书名,店铺,评价人数,链接(作为我们跳转的依据)是需要爬取的,这里就不考虑图片了。二级页面中的作者,出版社,出版时间。
在items中定义
import scrapyclass 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()#URLauthor=scrapy.Field()#作者press=scrapy.Field()#出版社publishTime=scrapy.Field()#出版时间
在爬取的时候没有考虑数据类型全是字符串,后期在做处理。
写具体爬虫方法
from copy import deepcopy #这里是考虑了数据丢失问题,不然二级页面中数据无法获取import scrapyclass SuningspiderSpider(scrapy.Spider):name = 'suningSpider'allowed_domains = ['']start_urls = ['/1-502320-0.html']def parse(self, response):book_lists = response.xpath('//div[@id="filter-results"]/ul/li')print(len(book_lists))item = {}count = 0for 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 + 1def 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 itemprint(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给Downloadertime.sleep(5) # 延时5s 待网页完全加载后打印html文本print("访问:", request.url)print("current_url", self.browser.current_url)urlchangecnt = 0body = ""while (urlchangecnt < 3) :self.browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')#这里下滑是因为进入页面只有30本书,要想得到整个页面的图书就需要下滑到底端动作,则数据是60本书time.sleep(5)#等待页面加载body = body + self.browser.page_sourceself.browser.find_element_by_id('nextPage').send_keys(Keys.ENTER)urlchangecnt = urlchangecnt + 1return HtmlResponse(self.browser.current_url, body=body,encoding="utf-8", request=request) # 这里必须返回Response的类或派生类#只有这样Scrapy才不会返回Request给Downloader
pipelines中去填写导入数据库的方法,以及数据的清洗
import pymongofrom scrapy.utils.project import get_project_settingssettings = 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=27017MONGODB_DBNAME='suning' #数据库名MONGODB_DOCNAME='book_info' #集合的名字# Crawl responsibly by identifying yourself (and your website) on the user-agent#USER_AGENT = 'suningBook (+)'# Obey robots.txt rulesROBOTSTXT_OBEY = False #LOG_LEVEL = 'WARNING' #为了使得控制台输出整洁调整了输出的等级,并且报错会在本地生产log。txt的文件LOG_FILE = 'log.txt'CONCURRENT_REQUESTS = 100COOKIES_ENABLED = FalseRETRY_ENABLED = FalseDOWNLOAD_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来就行。
以下是数据库截图
整个项目:/s/1hOR4xJd8JLA_seu9ZwNz8A
提取码:e0zd