PeekpaHub全栈开发教程【节选内容】

大家好,铲屎官最近正在忙着为大家编写《PeekpaHub全栈开发》的教程文章,会从"环境搭建"到“爬虫开发”,再到“后台开发”,最后“部署云服务器”一整套流程。

内容菜单部分内容

整个教程铲屎官会截图,细细讲解,专门针对那些:想学习Python的同学,想学习后端技术的同学,在校大学生,想拿项目来实战的同学而准备的。

项目体验地址:http://140.143.9.16:8000/

PeekpaHub

看到没?需要输入邀请码!文章里面找找看?

下面这些内容,是整个教程的第二章部分节选,多多少少能给大家带来一些帮助。如果有什么写的不对的地方,欢迎大家指出来。

=====以下内容为试读内容=====

二、开发爬虫程序

1. 创建Scrapy工程

首先,我们需要创建Scrapy工程。打开你的终端(Terminal,或者cmd),来到你要创建工程的目录,输入$ scrapy -help来查看Scrapy的帮助文档,会出现以下画面:

jc006

在这里你可以看到,如果Scrapy创建工程的话,需要输入的命令为

jc007

可以看到,Scrapy给了我们提示,首先得进入到PeekpaHubSpider文件夹中,命令$ cd PeekpaHubSpider,然后可以通过scrapystartproject<工程名>,那么我们输入¨C7C,看到会出现以下画面:![jc007](https://mmbiz.qpic.cn/mmbizp​ng/jA4Qc7C9IZTjTS4QfYequbyfZ0sNZjrAUuqBaEIIuzibfx2yVCeKdAj1uts0AiaGBYEKs9lOCWQOxw2yzXu8ibficg/0?wxf​mt=png)可以看到,Scrapy给了我们提示,首先得进入到PeekpaHubSpider文件夹中,命令¨C8C,然后可以通过 scrapy genspider example example.com来创建爬虫。那么,我们就通过Scrapy命令行,来按照模板创建一个爬虫,在终端输入$ scrapy genspider PeekpaHub peekpahub.com来创建爬虫。创建好之后,我们用PyCharm打开工程。如果按照上述步骤,打开的工程应该长这个样子:

jc008

我们来简单说一下这些个文件都有什么用:

  • PeekpaHub.py —— 这个就是我们的Spider文件,主要的代码就在这里面编写。
  • items.py —— 这个是爬虫在爬取到数据之后,需要把所有的数据封装成我们事先定义好的对象中。
  • middlewares.py —— Scrapy中间层文件,这里面编写的代码主要是在爬虫运行期间执行的操作,比如添加表头,设置IP代理。
  • pipelines.py —— 这里主要对item的处理。是添加到数据库还是写到文件里,这些操作一般都在这里进行。
  • settings.py —— 整个Scrapy框架的配置文件。
  • scrapy.cfg —— 整个是部署Scrapy的配置文件,和Scrapyd与Scrapyd-client一起使用。

OK,到此,我们第一个可以运行的爬虫程序就已经完成了。运行Scrapy的爬虫,指令是 $ scrapy crawl <爬虫名>,我们这里,为了方便运行,在 scrapy.cfg 文件同级的地方,我们创建一个 Run.py 文件,写入以下代码:

1from scrapy import cmdline
2
3def main():
4    cmdline.execute("scrapy crawl PeekpaHub".split())
5
6if __name__ == '__main__':
7    main()
复制代码

这样,每次只需要运行这个 Run.py 文件就可以直接运行我们的爬虫了。

但是,这个时候,如果你运行 Run.py 的话,控制台会报错:

jc009

那是因为,我们当初创建 Spider 的时候,输入的地址是 peekpahub.com 。实际中,这个域名是没有被解析的,所以会报错。那么我们把 PeekpaHub.py 文件中的 start_urls = ['http://peekpahub.com/'] 里面的网址换掉,比如换成 start_urls = ['http://www.baidu.com/'] ,这个时候如果我们再运行 Run.py ,那么就能出来正常的结果了。

jc010

至此,我们先放一放爬虫程序,接下来分析一下我们要爬取的页面的结构。

2. 分析HTML

网络页面,基本上都是用 HTML 的形式展示,HTML是一种超文本标记语言,何为标记语言?就是通过标签来区分的内容。既然是通过标签区分,那么我们就能够很轻松的从一个网页上面提取出来我们想要的内容。

我们要爬取的零食页面的URL如下:

https://search.jd.com/Search?keyword=%E9%9B%B6%E9%A3%9F&enc=utf-8&wq=lingshi

这里可能会发现,我们把上述 URL 复制到浏览器里,会直接显示:https://search.jd.com/Search?keyword=零食&enc=utf-8&wq=lingshi ,发现零食二字被转换成了%E9%9B%B6%E9%A3%9F,看上去像是乱码。大家别慌,这种其实是叫做encodeURI,大家可以去这个网站来自由转换汉字和URL编码:http://tool.oschina.net/encode

怎么分析网页?铲屎官在这里给大家详细的讲述一下。

首先,推进使用Chrome浏览器,打开我们上面的URL,然后,在你要查看的元素上面,点击右键,选择检查,就会弹出Chrome 的 DevTool 。这个工具超级好用。

jc011
jc012

就是用这种方法,我们来在茫茫的HTML里面来寻找我们想要的东西。

我们想要的东西有这些:

  1. 品牌列表 ![jc013]()
  2. 每个商品的入口特征![jc014]()
  3. 下一页的入口![jc015]()

好的,那么我们就按照这三个需求来找。首先是品牌列表,在品牌列表里面随便选择一个,点击右键,选择检查,看到 HTML 里面是这样的:

jc016

我们发现,如果要获取品牌名字,就是在一个 <a> 标签里,有一个 onclick 属性中,提到了品牌名字。那么我我们之后要尝试着在代码里面把这个提取出来。

接下来是每件商铺的入口特征。这个很简单,首先,随便选择一件商品,点击进去,会打开新的页面,比如,我们选择猪肉铺,点击打开进去之后我们观察页面的 URL 为:https://item.jd.com/4411011.html

jc017

在搜索页面,在图片的位置,右键,选择检查,看到:

jc018

在这个 HTML 里面,我们发现,这个商品详情页的地址实际也是在一个 <a> 标签里,href属性。同样,我们测试其他的商品,均为这个规则,那么之后,我们会在代码里面把这个详情页的 URL 提取出来。

最后是下一页的问题,这个就有点 trick 了。首先,页面的最下方,有分页符:

jc019

我们分别点击第二页和第三页,观察一下URL的变化。

第二页:https://search.jd.com/Search?keyword=%E9%9B%B6%E9%A3%9F&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=lingshi&stock=1&s=56&click=0&page=3

第三页:https://search.jd.com/Search?keyword=%E9%9B%B6%E9%A3%9F&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=lingshi&stock=1&s=109&click=0&page=5

分析这两个,唯一不同的就是 page 的后面的数字,我们可以通过变换后面的数字,来达到翻页的效果。

3. 明确数据结构

至此,我们分析完 HTML 结构,基本上就可以开始撸代码了。但是在撸代码之前,我们还是要明确一下我们最终我们存储的数据结构。

首先,我们所有的吃的是按照品牌来划分的,所以,一个品牌对应一个 Colloection (NoSQL) ,或者是 table (SQL)。

其次,我们的每一个零食,应当存储一下数据:

  • 零食的ID
  • 零食的名字
  • 零食的价格
  • 零食详情页的URL
  • 零食的优惠活动
  • 零食的店铺
  • 零食的图片
  • 零食的品牌
  • 零食的扫描时间
  • 零食的简单描述(涨价了还是降价了)

恩,就先这么多。那么我们接下来就要愉快的进入撸码环节了。

4. 撸码环节

1)规整结构

首先,我们得把之前Scrapy模板生成的Spider简单的改一下:

 1import scrapy
 2from bs4 import BeautifulSoup
 3
 4class PeekpahubSpider(scrapy.Spider):
 5    name = 'PeekpaHub'
 6    allowed_domains = ['search.jd.com']
 7    start_urls = ['https://search.jd.com/Search?keyword=%E9%9B%B6%E9%A3%9F&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=lingshi&stock=1&page=2&click=0']
 8
 9    def parse(self, response):
10        content = response.body
11        soup = BeautifulSoup(content, "html.parser")
12        print(content)
复制代码

将 第7行 的 start_urls 变量改为我们要爬取的 URL 。接着我们导入BeautifulSoup。 BeautifulSoup 是一款 Python 用来解析 HTML 语言的库。具体的用法,我们会在代码里面详细讲解。此时,如果运行我们的爬虫,在控制台打印出来的,是这个 HTML 页面的源码。

在 items.py 文件里面,我们需要创建我们爬取数据最后封装的对象类,这里面的代码应该修改成一下:

 1'''
 2items.py 文件
 3'''
 4class GoodsItem(scrapy.Item):
 5    # define the fields for your item here like:
 6    # name = scrapy.Field()
 7    goods_id = scrapy.Field()   # 零食的ID
 8    goods_title = scrapy.Field()   # 零食的名字
 9    goods_url = scrapy.Field()   # 零食的详情页URL
10    goods_img = scrapy.Field()   # 零食的图片
11    goods_price = scrapy.Field()   # 零食的价格
12    goods_shop = scrapy.Field()   # 零食的店铺
13    goods_icon = scrapy.Field()   # 零食的优惠活动
14    goods_time = scrapy.Field()   # 零食的扫描时间
15    goods_brand = scrapy.Field()   # 零食的品牌
16    goods_describe = scrapy.Field()   # 零食的描述
复制代码

创建好这个,为的是我们之后在爬虫里面,爬取到了我们需要的信息,就直接封装成 item ,然后交给 pipeline 了。

2)品牌列表

之前的分析中,我们看到,品牌列表在 HTML 中,是一个 <a> 标签,他的 onclick 属性是以 searchlog() 的结构开始的。那么这里我们来尝试的找一下,代码如下:

1    def parse(self, response):
2        content = response.body
3        soup = BeautifulSoup(content, "html.parser")
4        brand_temp_list = soup.find_all('a', attrs={"onclick": re.compile(r"searchlo(\w+)")})
5        brand_size = len(brand_temp_list)
6        print("total find: " + str(brand_size))
复制代码

这里可以看到,我们通过这种方法,找到的个数尽然有 586 个。

jc020

可能也就前十几个是我们需要的,所以,我们换一种思路,就是来找品牌标签上一级 <li> 标签,它的特征是 id="brand-XXXX"。其实这个很好找。那么我们把代码改成如下样子:

1    def parse(self, response):
2        content = response.body
3        soup = BeautifulSoup(content, "html.parser")
4        brand_temp_list = soup.find_all('li', attrs={"id": re.compile(r"brand-(\w+)")})
5        brand_size = len(brand_temp_list)
6        print("total find: " + str(brand_size))
7        print(brand_temp_list)
复制代码

这个时候我们看结果,很完美,找到匹配的有 40 个,在详细看里面的内容,确实都是品牌。

jc021

那么我们把品牌提取出来,只需要这么做:

1    def parse(self, response):
2        content = response.body
3        soup = BeautifulSoup(content, "html.parser")
4        brand_temp_list = soup.find_all('li', attrs={"id": re.compile(r"brand-(\w+)")})
5        brand_list = list()
6        for item in brand_temp_list:
7            brand_title = item.find('a')['title']
8            brand_list.append(brand_title)
9        print(brand_list)
复制代码

我们查看一下 brand_list 的结果:

jc022

果然,和我们预想的一样。但是,不知道大家发现没有,这里的品牌商品有个问题,就是品牌并不是单纯的汉字,一个品牌可能会带括号和英文,甚至标点符号,比如第一个就是 乐事(Lay\\s) ,这个品牌既带了括号,又带了英文,还带了标点符号,并不是简简单单的 乐事 。那么这里,我们就要对这些做一下处理了。这里我们使用 正则表达式 来提取品牌中的汉字。做法就是加一句代码:

1        for item in brand_temp_list:
2            brand_title = item.find('a')['title']
3            brand_list.append(re.sub("[A-Za-z0-9\!\%\[\]\,\。\(\)\(\)\"\.\'\ ]", "", brand_title))
复制代码

brand_list.append() 的时候,处理一下 brand_title ,这样处理完的结果,我们看到:

jc026

这样,从页面中提取品牌,我们就已经完成了。接下来就是寻找每个商品的入口了。

3)商品入口

商品入口,我们处理流程和品牌列表是一样的。首先是在任意商品图片点击右键,检查,打开之后如下:

jc023

发现,每一个商品其实都是一个 <li> 标签, 他有特殊标记:class=“gl-item” ,那么,我们就来寻找一下这个标签:

 1    def parse(self, response):
 2        content = response.body
 3        soup = BeautifulSoup(content, "html.parser")
 4        brand_temp_list = soup.find_all('li', attrs={"id": re.compile(r"brand-(\w+)")})
 5        brand_list = list()
 6        for item in brand_temp_list:
 7            brand_title = item.find('a')['title']
 8            brand_list.append(brand_title)
 9
10        goods_temp_list = soup.find_all('li', attrs={'class': 'gl-item'})
11        print(goods_temp_list)
复制代码

此时运行,发现我们找到了 30 个符合条件的 <li>,而且这些 <li> 全部是商品的模块。那么我们接着把我们需要的东西提取出来,零食ID,零食名称,零食价格,零食图片,零食URL,零食店铺,零食的优惠,零食的品牌。这些东西,比较细琐,我们拆开来说。

1-零食的ID

检查一个零食,我们发现他的ID,其实就是在他的 <li> 标签里。属性是 data-sku

jc024

那这样就简单了,我们可以直接爬取出来:

1        goods_temp_list = soup.find_all('li', attrs={'class': 'gl-item'})
2        for item in goods_temp_list:
3            goods = GoodsItem()
4            goods_id = item['data-sku']
复制代码

结果对照:

jc028
2-零食的名字

零食的名字,这个处理起来其实有一些诡。我们还是在每个零食的名字上,点击右键,检查:

jc027

发现,零食的名字是在一个拥有 class="p-name"<div> 标签中的 <em> 标签里,那么我们就按照这个层次寻找:

1        temp_div_title_list = item.find_all('div', attrs={'class': 'p-name'})
2        temp_em_title_list = temp_div_title_list[0].find('em')
3        goods_title = temp_em_title_list.text
复制代码

首先是通过 find_all() 方法来找到 <div> ,然后,在div里面寻找 <em> 标签,最后,把里面的文本信息提取出来,保存到 goods_title 中就可以啦。

结果对照:

jc029
3-零食的图片和详情页url

零食详情页URL和图片,同样我们还是在图片上面点击右键,然后检查:

jc030

发现,这些信息实际是在一个拥有 class="p-img"<div> 标签中,详情页的URL 在 <a> 标签里的 href 属性,图片的地址,是在 <img> 标签中的 src 属性。那么我们接着码代码:

1        temp_div_url_img_list = item.find_all('div', attrs={'class': 'p-img'})
2        goods_url = temp_div_url_img_list[0].find('a')['href'] 
3        temp_img = temp_div_url_img_list[0].find('img')
4        goods_img = temp_img['src']
复制代码

但是,如果要是这么运行的话,会报错,说 temp_img 里面没有“src“这个属性 。那么我们就打断点,来看一下这个 temp_img 到底是什么:

jc031
1<img class="err-product" data-img="1" height="220" source-data-lazy-img="//img13.360buyimg.com/n7/jfs/t21679/22/694083500/810221/a991e513/5b149695N8225ba76.jpg" width="220">
2</img>
复制代码

可以看到,上面的这个 <img> 标签里,确实没有 src 这个属性,反而是有一个叫 source-data-lazy-img 的属性。那么我们尝试的在浏览器里面看看能不能打开这个属性里面的值,结果是,去掉开头两个 // ,是完全可以打开图片的。为什么会这样?这是一种图片的懒加载模式,由于一般来说,图片的大小都要比纯网页内容大很多,所以,为了保证打开的流畅度,一般都是先加载网页数据,再加载媒体资源,媒体资源也就是懒加载。所以这里,我们这里就直接读取这个 source-data-lazy-img 的值作为我们 img 的值。代码如下:

1        temp_div_url_img_list = item.find_all('div', attrs={'class': 'p-img'})
2        goods_url = temp_div_url_img_list[0].find('a')['href'] if "http" in temp_div_url_img_list[0].find('a')['href'] else "http://" + temp_div_url_img_list[0].find('a')['href'][2:]
3        goods_img = "http://" + temp_div_url_img_list[0].find('img')['source-data-lazy-img'][2:]
复制代码

这里简单数一下,在读取 goods_url 的时候,我们用了 Python三元表达式 的写法,初学者可能看起来会头疼,他的作用其实等效于以下代码:

1    goods_url = ""
2    if "http" in temp_div_url_img_list[0].find('a')['href']:
3        goods_url = temp_div_url_img_list[0].find('a')['href']
4    else:
5        goods_url = "http://" + temp_div_url_img_list[0].find('a')['href'][2:]
复制代码

关于三元表达式,有兴趣的同学可以参考:python中的三元表达式(三目运算符)

goods_img 的获取过程中,用到了一个 [数字:数字] ,这个是Python字符串的切片功能,相关知识点很基础,在这里就不做过多解释,有需要的同学可以参考以下链接:廖雪峰Python教程 -- 切片

最后,上面的代码运行结果我们可以看到:

jc032
4-零食的价格

零食的价格,我们同样检查,发现:

jc033

在一个拥有 class="p-price"<div> 标签里的 <i> 标签的 text 而已,这个很简单,我们就直接提取就好:

1    temp_div_price_div = item.find_all('div', attrs={'class': 'p-price'})
2    goods_price = temp_div_price_div[0].find('i').text
复制代码

运行结果可以顺利拿到价格:

jc034
5-零食的店铺

零食的店铺,右键,检查:

jc035

发现,店铺信息是在一个拥有 class="p-shop"<div> 标签里的 <a> 标签的 text 值。这里要注意一点,在实际编代码的过程中,并不是每个 <div class="p-shop"> 里面都会有 <a> 标签,有些商品的店铺信息是空的。所以,我们这列的代码就应该写成以下样子:

1    temp_div_shop_list = item.find_all('div', attrs={'class': 'p-shop'})
2    temp_shop_list = temp_div_shop_list[0].find('a')
3    goods_shop = "" if temp_shop_list is None else temp_shop_list.text
复制代码

goods_shop 的时候,用一个三元表达式来处理。运行结果如下图:

jc036
6-零食的优惠

零食的优惠,就是每个图片最下方那些 200-100 之类的标签,我们检查发现:

jc037

这些数据是在一个一个拥有 class="p-icon"<div> 标签里面的 <i> 列表里。那么我们的代码就很简单啦:

1    temp_div_icon_list = item.find_all('div', attrs={'class': 'p-icons'})
2    temp_icon_list = temp_div_icon_list[0].find_all('i')
3    goods_icon = ""
4    for icon in temp_icon_list:
5        goods_icon = goods_icon + "/" + icon.text
复制代码

先找出来 <div> 标签,然后在循环找出来 <i> 标签,再把 text 添加进去,最后拼装成一个字符串。

jc038
7-零食的品牌

零食的品牌,说实话,这里不是很好获取,铲屎官在这里想了一个折中的办法,就是拿 goods_title 和之前的 brand_list 里面的做字符串的比较,如果 goods_title 里面包含了 brand ,那么就说明此商品有品牌,否则返回的是 No-brand 。代码如下:

1    goods_brand = self.getGoodsBrand(goods_title, brand_list)
2
3def getGoodsBrand(self, goods_title, brand_list):
4    for brand in brand_list:
5        if brand in goods_title:
6            return brand
7    return "No-brand"
复制代码

运行结果就如下所示:

jc039
8-零食的扫描时间和零食的描述

为什么会添加这两个变量?原因就在于,为了方便日后数据库的更新和内容的显示,比如,今天扫描了 三只松鼠的坚果是 108 元,然后明天在搜索这个选项的时候,发现价格变化了,那么就可以写在描述里,记录到数据库中。将来方便在网页上面显示,这两个数据其实很简单:

1    cur_time = datetime.datetime.now()
2    cur_year = str(cur_time.year)
3    cur_month = str(cur_time.month) if len(str(cur_time.month)) == 2 else "0" + str(cur_time.month)
4    cur_day = str(cur_time.day) if len(str(cur_time.day)) == 2 else "0" + str(cur_time.day)
5    goods_time = cur_year + "-" + cur_month + "-" + cur_day
6
7    goods_describe = ""
复制代码
9-大整合

好了,到此位置,我们把所有的关于商品的变量参数都从网页里面读取出来了,那么接下来,我们就是要把这些东西塞到我们之前创建好的 GoodsItem 里面了。很简单,创建一个 GoodsItem ,然后以键值对的形式赋值就好,最后,别忘了调用 yield 方法,将 goods 给抛出来:

 1    goods = GoodsItem()
 2    goods['goods_id'] = goods_id
 3    goods['goods_title'] = goods_title
 4    goods['goods_url'] = goods_url
 5    goods['goods_img'] = goods_img
 6    goods['goods_price'] = goods_price
 7    goods['goods_shop'] = goods_shop
 8    goods['goods_icon'] = goods_icon
 9    goods['goods_time'] = goods_time
10    goods['goods_brand'] = goods_brand
11    goods['goods_describe'] = ""
12    yield goods
复制代码

只有添加了 yield 方法,当程序运行时,控制台才会打出来每一个item的详情:

jc040

这一节的代码,均在项目的 spiders/PeekpaHub.py 文件里,详情代码可以去文件中详细阅读。

4) 下一页的爬取

到此位置,我们可以说,这一页的网页信息,我们已经爬取完毕了。一页只有30条数据,这是远远不够的。在之前我们分析的,不同页之前,URL的区别就是 page参数的区别。那么我们这里就很简单了,只需要变换最后一个数字就可以。

 1class PeekpahubSpider(scrapy.Spider):
 2    name = 'PeekpaHub'
 3    allowed_domains = ['search.jd.com']
 4    max_page = 10
 5    start_urls = ['https://search.jd.com/Search?keyword=%E9%9B%B6%E9%A3%9F&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=lingshi&stock=1&click=0&page=1']
 6
 7    def parse(self, response):
 8        '''
 9        中间代码略过
10        '''
11        cur_page_num = int(response.url.split('&page=')[1])
12        next_page_num = cur_page_num + 1
13        if cur_page_num < self.max_page:
14            next_url = response.url[:-len(str(cur_page_num))] + str(next_page_num)
15            yield Request(url=next_url, callback=self.parse, dont_filter=True)
复制代码

这里,我们通过 response.url 来首先获取到当期页面的 URL ,然后,用很暴力的方法,将 URL 以 &page= 来做字符串的划分,那么,这个数组的第二个元素,就是我们当前页面的页数。所以,就把这个数字加一,然后再拼会之前的 URL 里面,组成下一页的 URL ,再调用 Request 方法,URL 设置成新的 URL,解析的 callback 设置成当前的解析函数 parse() ,最后在加一个变量 dont_filter=True , 这样,爬虫就会自动爬去下一页。防止无线爬取,加一个最大页数 max_page 。这里先设置的成 10 ,之后的 setting 章节会说道怎样设置这个值。先运行一下看看效果:

jc041

可以看到,爬虫确实是对 前10页 的内容进行爬取,然后就停止了,这个和我们预期的结果是一样的。

好了,到此位置,我们基本上就把我们的 Spider 文件编写完成了,接下来,我们要去处理在爬虫爬到数据 GoodsItem 之后,怎样将数据保存到 MongoDB 上了。

5) 数据保存到MongoDB上

保存数据到MongoDB上,我们这里的MongoDB是云服务器上的MongoDB,所以,安装配置在第一章里面就详细的讲过了,不清楚的同学可以去再看一下。

这里保存数据,就是把爬虫在爬取出来的每一个 GoodsItem 都塞到 MongoDB 里。而在 Scrapy 框架里,这部分操作,是在 pipelines.py 文件里面,通过编写 pipeline 来实现的。

我们先来看,Scrapy 使用模板生成的 pipelines.py 文件长什么样子:

1# Define your item pipelines here
2#
3# Don't forget to add your pipeline to the ITEM_PIPELINES setting
4# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
5
6
7class PeekpahubspiderPipeline(object):
8    def process_item(self, item, spider):
9        return item
复制代码

首先,最上面有几行注释,是模板里面自带的,它的意思是,如果需要使用 Pipelines ,不仅需要在这里编写你的代码逻辑, 同时,别忘了在 settings.py 文件里面,设置 ITEM_PIPELINES 变量 。就是长下面这个样子:

1'''
2settings.py 文件
3'''
4
5# Configure item pipelines
6# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
7ITEM_PIPELINES = {
8   'PeekpaHubSpider.pipelines.PeekpahubspiderPipeline': 300,
9}
复制代码

接着说,有一个 PeekpahubspiderPipeline 类,里面有一个 process_item() 方法,这个方法的调用时机,就是 spider 程序在拼装好一个 item ,调用 yield items 之后,来处理这个 item 的。所以,我们对数据存储在数据库的操作,就应该在这个 process_item() 方法里面来写。

这里我们需要Python代码来操作数据库,我们这里使用 pymongo库 来进行操作。

既然是数据库的操作,那么我们就躲不开两个很关键的操作:连接数据库 和 断开数据库 。

链接和断开,我们不可能爬虫每生成一个 item 就执行

连接数据库
-->
读写
-->
断开数据库
。这样操作太消耗数据库的 读写IO ,所以,我们最希望的模式就是
连接数据库
--> 读写 --> 。。。 --> 读写 -->
断开数据库
。就是爬虫一开始的时候,我们就连接数据库,然后在爬虫工作的期间,每生成一个 item ,就存储一个 item 就好,最后,爬虫关闭的时候,再断开数据库的连接。

恰好, Scrapy 的 pipeline 里面提供了这种操作。在 pipeline 里面,有两个方法,一个是 open_spider(self, spider) 另一个是 close_spider(self, spider) 。这两个方法,对应的就是 爬虫开始 和 爬虫结束 两个过程。所以,我们的数据库的连接操作和断开操作,在这两个方法里面完成就好。

 1    def open_spider(self, spider):
 2        self.client = pymongo.MongoClient("mongodb://localhost/", 27001)
 3        self.db = self.client["PeekpaHub"]
 4        self.collection = self.db["Lingshi"]
 5
 6    def close_spider(self, spider):
 7        self.client.close()
 8
 9    def process_item(self, item, spider):
10        self.collection.insert(dict(item))
11        return item
复制代码

上面的代码我们看到:

  • 首先通过 pymongo.MongoClient() 方法来创建一个 client ,传入参数是 mongodb 服务器的 IP 以及

    MongoDB 的端口号。

  • 其次,通过 client[“XXXX”] 来获取我们的 XXXX Databse,如果 MongoDB 中事先没有这个 Databse ,当插入数据的时候,那么会自己创建这个 Databse。这个概念在 SQL 数据库中就是 Databse。

  • 最后,通过 db["XXX"] 来拿到 XXX collection,如果 MongoDB 中事先没有这个 collection,当插入数据的时候,会自己按照item的格式来创建。这个概念在 SQL 数据库中,对应的是 Table。

那么,我们此时运行一下整个爬虫,在观察一下远端 MongoDB 里面会有什么变化。

jc051

我们惊奇的发现,远端的数据库里有数据啦!!!而且数据正常,啊哈哈哈哈哈,这里可以高兴一下。

这里有个小细节和大家说一下,如果仔细的同学,可能看到我说上面的diamante里面, databse 的名字叫 “PeekpsHubTest” ,其目的就是目前我们的项目正在开发阶段,不确定性很大,如果直接使用正式数据的数据库的话,风险很大。这里只是单纯的用不同名字的 Databse ,如果实际生产过程中,最好是用不同服务器的 MongoDB 来做开发。所以,建议大家以后开发的时候,现在测试环境和测试数据来发功能,等功能测试的差不多了,再切换数据环境。

我们打算在这里针对 Collection 做一下特殊处理。就是,针对几个大品牌,我们单独的抽出来做一个 Collection , 剩下的其他品牌和没有牌子的产品,我们单独再放到另一个 Collection 里。这样做的目的是为了日后 PeekpaHub 读取数据而做的准备。如果按照这种分类来做,我们的 process_item() 代码就变成了以下样子:

 1    def process_item(self, item, spider):
 2        if isinstance(item, GoodsItem):
 3            try:
 4                collection_name = self.getCollection(item['goods_brand'])
 5                if self.db[collection_name].find_one({"goods_id": item['goods_id']}) is None:
 6                    logging.info("items: " + item['goods_id'] + " insert in " + collection_name + " db.")
 7                    self.db[collection_name].insert(dict(item))
 8                else:
 9                    logging.info("items: " + item['goods_id'] + " has in " + collection_name + " db.")
10            except Exception as e:
11                logging.error("PIPLINE EXCEPTION: " + str(e))
12        return item
13
14    '''
15    brand_list = 
16    ['乐事', '旺旺', '三只松鼠', '卫龙', '口水娃', '奥利奥', '良品铺子', '达利园', '盼盼',
17     '稻香村', '好丽友', '徐福记', '盐津铺子', '港荣', '上好佳', '百草味', '雀巢', '波力',
18      '甘源牌', '喜之郎', '可比克', '康师傅', '嘉士利', '嘉华', '友臣', '来伊份', '豪士', 
19      '米多奇', '闲趣', '稻香村', '', '桂发祥十八街', '趣多多', '好巴食', '北京稻香村', '法丽兹',
20       '无穷', '源氏', '华美', '葡记']
21    '''
22    def getCollection(self, brand):
23        if brand == '乐事':
24            return "Leshi"
25        elif brand == '旺旺':
26            return "Wangwang"
27        elif brand == '三只松鼠':
28            return "Sanzhisongshu"
29        elif brand == '卫龙':
30            return "Weilong"
31        elif brand == '口水娃':
32            return "Koushuiwa"
33        elif brand == '奥利奥':
34            return "Aoliao"
35        elif brand == '良品铺子':
36            return "Liangpinpuzi"
37        else:
38            return "Linshi"
复制代码

这样写完,我们的目的就是,如果 item 的品牌是这几个当中,那么就将 item 存储到对应的 Collection 里面。这样我们跑一下爬虫,然后看一下我们的 MongoDB 里的情况:

jc052

完全正确。看到 PeekpaHubTest 的 Database 里面,有 8 个 Collection ,每个 Collection 里面存储了对应品牌的 item。可是,现在就有这么几个问题:

  • 爬虫写入数据库的时候,其实还得再判断一下
  • 我们爬取到的数据不够,这个可以通过扩大页数来弥补。
  • 爬虫不是很稳定,在之前开发调试的过程中,会在京东上请求道失败的数据,应该是京东做了反爬处理。

针对这几个问题,我们就逐一分析并且击破。

=====试读结束=====

整套教程还在编写中,预计一两周完成吧,真正意义上的全栈教材,如果你是在校大学生或者是想通过实战项目来让自己简历加分的童鞋,这个教程绝对是你的不二之选。更多最新动态,欢迎关注『

皮克啪的铲屎官
』。
体验邀请码『
peekpa
』,关注『
皮克啪的铲屎官
』,回复『
代码
』即可获得。回复内容保你满意。

粉一波自己的小程序

这么硬核的公众号,还不关注一波啊?


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值