Scrapy框架实战项目

1、创建项目:scrapy startproject tubatu_scrapy_project

2、梳理需要爬取的内容:

	爬取土巴兔
  1. url:https://xiaoguotu.to8to.com/tuce/【土巴兔的装修图册】
  2. 爬取的字段:在items.py文件声明需要爬取的字段👇
	content_name = scrapy.Field()   # 装修名称
    content_id = scrapy.Field()     # 装修id
    content_url = scrapy.Field()    # 请求的url地址
    nick_name = scrapy.Field()      # 上传图片人的昵称
    pic_url = scrapy.Field()        # 图片的url地址
    pic_name = scrapy.Field()       # 图片名称
  1. 通过模板生成spider文件:scrapy genspider tubatu xiaoguotu.to8to.com/tuce/

3、编写如下代码:

在这里插入图片描述
1》通过xpath获取该装修页面的所有信息块,如下图(各个信息块)所示:
在这里插入图片描述
2》遍历这些信息块,获取各个信息块的名称url(代码中粉色部分)
3》通过yield发送异步请求,传递两个参数:url回调函数。使用break(即只输出第一个信息块的内容),并在回调函数中输出第一个信息块的网页源码

4、进入到第一个信息块页面继续分析:

》第一个信息块页面的地址:https://xiaoguotu.to8to.com/c2967148.html 而该网址的后面一串数字编码就是装修id。该页面如下图所示👇:
在这里插入图片描述
》最终发现,若想要获取图片的名称图片的url上传图片人的昵称等信息需要通过浏览器的调式工具中的Network获取相应的json数据,里面便是需要用到的信息。
》该json数据地址:https://xiaoguotu.to8to.com/case/list?a2=0&a12=&a11=2967148&a1=0 【且该地址中的最后一串数字也是装修id】因此,我们的抓取数据的url地址更改为此json数据地址。该页面如下图所示👇
在这里插入图片描述
》最终编写如下代码:

  • 其中用正则获取网页上的装修id、并将json数据地址作为请求的url地址(在yield异步请求中作为url参数)
  • 导入之前编写的items.py中的字段,用for循环遍历json数据地址的内容,从而获取相应的字段数据,该代码爬取到相应的上传图片人的昵称图片的url地址图片名称
  • 但输出的内容有20条、且除了第一个信息块的信息完整以外,其他的信息块的信息都缺少图片名称。因此还需要继续完善。
# -*- coding: utf-8 -*-
import scrapy
import re
import json
from tubatu_scrapy_project.items import TubatuScrapyProjectItem


class TubatuSpider(scrapy.Spider):
    name = 'tubatu'     # 爬虫项目名称
    allowed_domains = ['xiaoguotu.to8to.com']     # 允许爬取的网址
    start_urls = ['https://xiaoguotu.to8to.com/tuce/p_1.html']  # 从哪个网页开始爬取

    # 默认的解析方法
    def parse(self, response):
        # print(response.text)    # 打印请求的网页的源码
        pic_item_list = response.xpath("//div[@oldaid]")  # 获取页面上的所有信息块
        # 因为response就是一个Html对象,所以response后面可以直接使用xpath方法

        # 使用正则提取url地址,以备从中获取id
        content_id_search = re.compile(r"(\d+)\.html")

        for item in pic_item_list:
            # 获取项目的名称
            content_name = item.xpath("./div/a/text()").extract_first()
            # extract_first()是xpath语句的方法,返回的是一个string字符串,是list数组里面的第一个字符串

            # 获取项目的url  该url地址包含id的信息,且该id有用处,所以需要在前面使用正则来提取
            content_url = "http://"+item.xpath("./div/a/@href").extract_first()

            # 使用正则提取id
            content_id = content_id_search.search(content_url).group(1)

            content_ajax_url = "https://xiaoguotu.to8to.com/case/list?a2=0&a12=&a11={}&a1=0&a17=1".format(content_id)

            # 使用yield来发送异步请求
            # 使用scrapy.Request()来发送请求,传递两个参数,其中callback(回调函数),只写方法的名称即可
            yield scrapy.Request(url=content_ajax_url, callback=self.handle_pic_parse)
            break

    def handle_pic_parse(self, response):
        # print(response.text) 返回的是json格式
        pic_dict_data = json.loads(response.text)["dataImg"]	# json.loads()方法将json格式数据转换为字典
        for pic_item in pic_dict_data:
            for item in pic_item["album"]:
                tubatu_info = TubatuScrapyProjectItem()     # 使用在items.py中定义的字段,因此需要导入相应的TubatuScrapyProjectItem包
                tubatu_info["nick_name"] = item["l"]["n"]   # 上传图片人的昵称
                tubatu_info["pic_url"] = "https://pic1.to8to.com/case/" + item["l"]["s"]    # 图片的url地址
                tubatu_info["pic_name"] = item["l"]["t"]    # 图片名称
                print(tubatu_info)

5、完善代码:

  • 将其他字段信息也引入。即:声明info字典,对其他字段封装到字典中,并在yield请求中传入meta参数
  • 并对json数据地址的内容进行条件判断,只获取有效的信息(有图片名称的信息才要,即:只获取第一个信息块【因为第一个信息块才有图片名称】)。

》因此代码如下:

# -*- coding: utf-8 -*-
import scrapy
import re
import json
from tubatu_scrapy_project.items import TubatuScrapyProjectItem


class TubatuSpider(scrapy.Spider):
    name = 'tubatu'     # 爬虫项目名称
    allowed_domains = ['xiaoguotu.to8to.com']     # 允许爬取的网址
    start_urls = ['https://xiaoguotu.to8to.com/tuce/p_1.html']  # 从哪个网页开始爬取

    # 默认的解析方法
    def parse(self, response):
        # print(response.text)    # 打印请求的网页的源码
        pic_item_list = response.xpath("//div[@oldaid]")  # 获取页面上的所有信息块
        # 因为response就是一个Html对象,所以response后面可以直接使用xpath方法

        # 使用正则提取url地址,以备从中获取id
        content_id_search = re.compile(r"(\d+)\.html")

        for item in pic_item_list:
            info = {}   # 创建字典,把需要的字段封装到字典中,如下:info["content_name"]等3个

            # 获取项目的名称
            info["content_name"] = item.xpath("./div/a/text()").extract_first()
            # extract_first()是xpath语句的方法,返回的是一个string字符串,是list数组里面的第一个字符串

            # 获取项目的url  该url地址包含id的信息,且该id有用处,所以需要在前面使用正则来提取
            info["content_url"] = "http://"+item.xpath("./div/a/@href").extract_first()

            # 使用正则提取id
            info["content_id"] = content_id_search.search(info["content_url"]).group(1)

            content_ajax_url = "https://xiaoguotu.to8to.com/case/list?a2=0&a12=&a11={}&a1=0&a17=1".format(info["content_id"])

            # 使用yield来发送异步请求
            # 使用scrapy.Request()来发送请求,传递两个参数,其中callback(回调函数),只写方法的名称即可
            yield scrapy.Request(url=content_ajax_url, callback=self.handle_pic_parse, meta=info)   # 传递meta参数,这样在回调函数中就可以获取到info
            break

    def handle_pic_parse(self, response):
        # print(response.request.meta)  返回前面info中的字段
        # print(response.text)  返回的是json格式
        pic_dict_data = json.loads(response.text)["dataImg"]
        for pic_item in pic_dict_data:
            for item in pic_item["album"]:
                if item["l"]["cid"] == int(response.request.meta["content_id"]):
                    tubatu_info = TubatuScrapyProjectItem()     # 使用在items.py中定义的字段,因此需要导入相应的TubatuScrapyProjectItem包
                    tubatu_info["nick_name"] = item["l"]["n"]   # 上传图片人的昵称
                    tubatu_info["pic_url"] = "https://pic1.to8to.com/case/" + item["l"]["s"]    # 图片的url地址
                    tubatu_info["pic_name"] = item["l"]["t"]    # 图片名称
                    tubatu_info["content_name"] = response.request.meta["content_name"]
                    tubatu_info["content_id"] = response.request.meta["content_id"]
                    tubatu_info["content_url"] = response.request.meta["content_url"]
                    print(tubatu_info)
  • 以上代码获取到的仅仅是第一个信息块的字段内容,若要获取首页中第一页的所有信息块内容,那么将yeild后面的break删除即可。

6、页码逻辑编写:

(该代码在for循环外面编写)
1》获取网页的页码,判断是否存在下一页
2》用xpath语法获取当前页面,并使用extract_first()获取,再用int()进行转型。并将当前页码数+1得到下一页的url地址
3》再次使用yield方法,url地址为下一页的url,回调函数为parse函数

# 页码编辑
if response.xpath("//a[@id='nextpageid']"):	
	now_page = int(response.xpath("//div[@class='pages']/strong/text()").extract_first())
	next_page = "https://xiaoguotu.to8to.com/tuce/p_{}.html".format(now_page+1)
	yield scrapy.Request(url=next_page, callback=self.parse)

7、代码的存储:

在最后返回的数据中调用yield方法,将数据返回给pipelines.py,在pipelines.py中编写代码的存储操作

   tubatu_info["content_id"] = response.request.meta["content_id"]
   tubatu_info["content_url"] = response.request.meta["content_ajax_url"]
   yield tubatu_info

而且需要在settings.py文件中对pipelines的设置项进行打开,如下图所示:👇在这里插入图片描述
pipelines.py中编写如下代码即可连接mongodb,从而将数据存储在mongodb

import pymongo

class TubatuScrapyProjectPipeline(object):
    def __init__(self):     # 初始化mongodb方法
        client = pymongo.MongoClient("mongodb://localhost:27017")
        client.admin.authenticate("admin", "admin")	# 因为自己设置了mongodb的权限认证,所有需要额外加上这句,如果没有设置权限认证就不用添加这一段代码
        db = client["db_tubatu"]
        self.collection = db["collection_tubatu"]

    def process_item(self, item, spider):
        data = dict(item)   # 转为字典格式
        self.collection.insert(data)
        return item

8、伪装爬虫:

1)通过修改请求头User-Agent

  1. middlewares.py文件中自定义请求头中间件,编写如下代码:
# 自定义爬虫中间件(修改请求头)
class MyMiddleware(object):
    def process_request(self, request, spider):
        # 网上搜索user_agent,封装成一个列表
        user_agent_list = [
            'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60',
            'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
            ....(省略其他user_agent)
        ]

        agent = random.choice(user_agent_list)  # 导入random模块,使用random.choice()随机选取列表中的一个数据
        request.headers["User-Agent"] = agent   # 设置请求头
  1. 再到settings.py中开启中间件选项,并把自定义中间件的名称(MyMiddleware)添加上,后面的数值表示优先级,数值越小,优先级越大

在这里插入图片描述
2)通过设置代理

  1. middlewares.py文件中自定义代理中间件,编写如下代码:
# 自定义爬虫中间件(设置ip代理)
class MyProxy(object):
    def process_request(self, request, spider):
        request.meta["proxy"] = "b5.t.16yun.cn:6460"            # 代理ip:端口号
        proxy_name_pass = 'admin:123321'.encode("utf-8")        # 代理用户名:密码
        encode_pass_name = base64.b64encode(proxy_name_pass)    # 导入b64模块,使用b64进行加密处理

        # 将代理信息设置到头部    【注意!在Basic后面有一个空格, 并使用decode()转化为字符串格式】
        request.headers["Proxy-Authorization"] = "Basic "+encode_pass_name.decode()
  1. 同样的,再到settings.py中把自定义中间件的名称(MyProxy)添加上

在这里插入图片描述

9、图片存储:

1》在items.py文件中使用如下字段,用于存储图片(在pic_url后面添加)

pic_url = scrapy.Field()      # 图片的url地址
image_urls = scrapy.Field()     # 若要下载图片保存到磁盘中,那么该字段是必须的!!!

2》在spider文件中使用如下字段,同样的在pic_url后面添加

tubatu_info["pic_url"] = "https://pic1.to8to.com/case/" + item["l"]["s"]    # 图片的url地址

# 必须使用该字段,且后面的数据要改成列表的格式(加多个[]即可)
tubatu_info["image_urls"] = ["https://pic1.to8to.com/case/" + item["l"]["s"]]

3》在pipelines.py文件中自定义图片下载类,进行图片存储的代码编写,实现图片存储在不同的文件夹中

import scrapy
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline

# 自定义图片下载类,且继承于ImagesPipeline
class TubatuImagePipeline(ImagesPipeline):
	def get_media_requests(self, item, info):
		# 根据image_urls中指定的url进行爬取
        for img_url in item['image_urls']:
            # 获取到item中的image_urls数据,传递到下面进行下载
            # 再将item中的数据传入到meta参数中,用于后面存储图片时的命名
            yield scrapy.Request(img_url, meta={'item': item})

	# 该方法默认即可,一般不做修改(用于下载图片)
    def item_completed(self, results, item, info):
        # 图片下载完成之后,处理结果的方法,返回的是一个二元组
        # 返回的格式:(success, image_info_or_failure)  第一个元素表示图片是否下载成功;第二个元素是一个字典,包含了image的信息
        image_paths = [x["path"] for ok, x in results if ok]    # 通过列表生成式
        if not image_paths:
            raise DropItem("Item contains no images")   # 抛出异常,图片下载失败(注意要导入DropItem模块)
        return item
	
	 # 用于给下载的图片设置文件名称和路径
	def file_path(self, request, response=None, info=None):
        item = request.meta['item']		# 从上面的get_media_requests()方法中获取到item
        folder = item['content_name']	# 获取爬取到的数据的content_name
        folder_strip = folder.strip()	# 去除空格
        image_guid = request.url.split('/')[-1]	 # request.url 获取到如下的图片地址
        # 如:"https://pic.to8to.com/case/2018/08/27/20180827165930-fac62168_284.jpg"
        # 拆分后为:20180827165930-fac62168_284.jpg
        filename = u'images/{}/{}'.format(folder_strip, image_guid)	
        return filename

-----解释上面的第13行代码:image_paths = [x['path'] for ok, x in results if ok],(其实这是列表推导式,只是python中的简写写法)

  • result是一个包含tuple的容器
  • 容器中每个元素包含两个值,第一个代表状态True/False,第二个值是一个dict
  • 如果元素中状态为True则取dict中的path值

拆开来写就是:

for ok, x in results:
    if ok:
        print(x['path'])

4》最后在settings.py文件中进行如下配置
在这里插入图片描述
效果如下图:
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值