Scrapy-爬取安智市场app详情

算法寻星家
• 阅读 1905

前言

本篇文章是利用Scrapy扒取安智市场的app详情页,如点击查看和平精英,包括app名、版本号、图标icon、分类、时间、大小、下载量、作者、简介、更新说明、软件截图、精彩内容等,扒取的图片资源icon和市场展示图(app截图)下载到本地,并将所有数据存储到数据库。

考虑的问题:

  • 存储的数据库设计
  • 图片资源链接存在重定向
  • 下载app的图标需为.png后缀
  • ...

需要先熟悉Scrapy框架的同学:点击学习

数据库设计

创建的为mysql数据库,名称为app_anzhigame,表名为games,安智市场的市场图限制为4-5张,简介等为1500字以内,图片均为相对地址

# 建库
CREATE DATABASE app_anzhigame CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

USE app_anzhigame;
DROP TABLE games;

# 建表
CREATE TABLE games(
  id INTEGER(11)  UNSIGNED AUTO_INCREMENT COLLATE utf8mb4_general_ci,
  name VARCHAR(20) NOT NULL COLLATE utf8mb4_general_ci COMMENT '游戏名' ,
  versionCode VARCHAR(10) COLLATE utf8mb4_general_ci COMMENT '版本号' NOT NULL DEFAULT 'v1.0',
  icon VARCHAR(100) COLLATE utf8mb4_general_ci COMMENT '游戏图标icon' NOT NULL DEFAULT '',
  type VARCHAR(20) COLLATE utf8mb4_general_ci COMMENT '分类' NOT NULL DEFAULT '',
  onlineTime VARCHAR(20) COLLATE utf8mb4_general_ci COMMENT '上线时间',
  size VARCHAR(10) COLLATE utf8mb4_general_ci COMMENT '大小' NOT NULL DEFAULT '0B',
  download VARCHAR(10) COLLATE utf8mb4_general_ci COMMENT '下载量' NOT NULL DEFAULT '0',
  author VARCHAR(20) COLLATE utf8mb4_general_ci COMMENT '作者',
  intro VARCHAR(1500) COLLATE utf8mb4_general_ci COMMENT '简介',
  updateInfo VARCHAR(1500) COLLATE utf8mb4_general_ci COMMENT '更新说明',
  highlight VARCHAR(1500) COLLATE utf8mb4_general_ci COMMENT '精彩内容',
  image1 VARCHAR(100) COLLATE utf8mb4_general_ci COMMENT '市场图1',
  image2 VARCHAR(100) COLLATE utf8mb4_general_ci COMMENT '市场图2',
  image3 VARCHAR(100) COLLATE utf8mb4_general_ci COMMENT '市场图3',
  image4 VARCHAR(100) COLLATE utf8mb4_general_ci COMMENT '市场图4',
  image5 VARCHAR(100) COLLATE utf8mb4_general_ci COMMENT '市场图5',
  link VARCHAR(200) COLLATE utf8mb4_general_ci COMMENT '爬取链接',
  create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE current_timestamp COMMENT '更新时间',
  PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT '安智市场爬取游戏列表';

创建item

创建项目scrapy startproject anzhispider,修改items.py

class AnzhispiderItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 链接地址
    link = scrapy.Field()
    # app名称
    name = scrapy.Field()
    # 版本号
    versionCode = scrapy.Field()
    # 游戏图标icon
    icon = scrapy.Field()
    # icon存储地址
    iconPath = scrapy.Field()
    # 分类
    type = scrapy.Field()
    # 上线时间
    onlineTime = scrapy.Field()
    # 大小
    size = scrapy.Field()
    # 下载量
    download = scrapy.Field()
    # 作者
    author = scrapy.Field()
    # 简介
    intro = scrapy.Field()
    # 更新说明
    updateInfo = scrapy.Field()
    # 精彩内容
    highlight = scrapy.Field()
    # 市场图  字符数组
    images = scrapy.Field()
    # 市场图存储地址
    imagePaths = scrapy.Field()

创建Spider

spiders目录下创建AnzhiSpider.py,并创建class AnzhiSpider,继承于scrapy.Spider。

class AnzhiSpider(Spider):
    name = "AnzhiSpider"
    # 允许访问的域
    allowed_domains = ["www.anzhi.com"]

    start_urls = ["http://www.anzhi.com/pkg/3d81_com.tencent.tmgp.pubgmhd.html"]

    # start_urls = ["http://www.anzhi.com/pkg/3d81_com.tencent.tmgp.pubgmhd.html","http://www.anzhi.com/pkg/84bf_com.sxiaoao.feijidazhan.html","http://www.anzhi.com/pkg/4f41_com.tencent.tmgp.WePop.html"]

    def parse(self, response):
        item = AnzhispiderItem()
        root = response.xpath('.//div[@class="content_left"]')
        # 链接
        item['link'] = response.url
        # 图标
        item['icon'] = root.xpath('.//div[@class="app_detail"]/div[@class="detail_icon"]/img/@src').extract()[0]
        # app名称
        item['name'] = root.xpath(
            './/div[@class="app_detail"]/div[@class="detail_description"]/div[@class="detail_line"]/h3/text()').extract()[
            0]
        # 版本号
        item['versionCode'] = root.xpath(
            './/div[@class="app_detail"]/div[@class="detail_description"]/div[@class="detail_line"]/span[@class="app_detail_version"]/text()').extract()[
            0]
        if item['versionCode'] and item['versionCode'].startswith("(") and item['versionCode'].endswith(")"):
            item['versionCode'] = item['versionCode'][1:-1]

        # 分类、上线时间、大小、下载量、作者  先获取所有的详情
        details = root.xpath(
            './/div[@class="app_detail"]/div[@class="detail_description"]/ul[@id="detail_line_ul"]/li/text()').extract()
        details_right = root.xpath(
            './/div[@class="app_detail"]/div[@class="detail_description"]/ul[@id="detail_line_ul"]/li/span/text()').extract()
        details.extend(details_right)

        for detailItem in details:
            if detailItem.startswith("分类:"):
                item['type'] = detailItem[3:]
                continue
            if detailItem.startswith("时间:"):
                item['onlineTime'] = detailItem[3:]
                continue
            if detailItem.startswith("大小:"):
                item['size'] = detailItem[3:]
                continue
            if detailItem.startswith("下载:"):
                item['download'] = detailItem[3:]
                continue
            if detailItem.startswith("作者:"):
                item['author'] = detailItem[3:]
                continue

        # 简介
        item['intro'] = root.xpath(
            './/div[@class="app_detail_list"][contains(./div[@class="app_detail_title"],"简介")]/div[@class="app_detail_infor"]').extract()
        if item['intro']:
            item['intro'] = item['intro'][0].replace('\t', '').replace('\n', '').replace('\r', '')
        else:
            item['intro'] = ""
        # 更新说明
        item['updateInfo'] = root.xpath(
            './/div[@class="app_detail_list"][contains(./div[@class="app_detail_title"],"更新说明")]/div[@class="app_detail_infor"]').extract()
        if item['updateInfo']:
            item['updateInfo'] = item['updateInfo'][0].replace('\t', '').replace('\n', '').replace('\r', '')
        else:
            item['updateInfo'] = ""
        # 精彩内容
        item['highlight'] = root.xpath(
            './/div[@class="app_detail_list"][contains(./div[@class="app_detail_title"],"精彩内容")]/div[@class="app_detail_infor"]').extract()
        if item['highlight']:
            item['highlight'] = item['highlight'][0].replace('\t', '').replace('\n', '').replace('\r', '')
        else:
            item['highlight'] = ""

        # 市场图地址
        item['images'] = root.xpath(
            './/div[@class="app_detail_list"][contains(./div[@class="app_detail_title"],"软件截图")]//ul/li/img/@src').extract()
        yield item

下载icon和市场图

创建ImageResPipeline并继承于from scrapy.pipelines.files import FilesPipeline,不用ImagesPipeline的原因可以查看ImagesPipeline官网的解释,它的主要功能为:

  • 将所有下载的图片转换成通用的格式(JPG)和模式(RGB)
  • 避免重新下载最近已经下载过的图片
  • 缩略图生成
  • 检测图像的宽/高,确保它们满足最小限制

划重点下载的图片为jpg格式,小编需要下载icon为png格式的,需要图标为无背景的,采用ImagesPipeline图片就算进行类型转换还是不能去掉背景,这样会导致圆角的图标空缺被白色补满。

class ImageResPipeline(FilesPipeline):
    def get_media_requests(self, item, info):
        '''
        根据文件的url发送请求(url跟进)
        :param item:
        :param info:
        :return:
        '''
        # 根据index区分是icon图片还是市场图
        yield scrapy.Request(url='http://www.anzhi.com' + item['icon'], meta={'item': item, 'index': 0})
        # 市场图下载
        for i in range(0, len(item['images'])):
            yield scrapy.Request(url='http://www.anzhi.com' + item['images'][i], meta={'item': item, 'index': (i + 1)})

    def file_path(self, request, response=None, info=None):
        '''
        自定义文件保存路径
        默认的保存路径是在FILES_STORE下创建的一个full来存放,如果我们想要直接在FILES_STORE下存放或者日期路径,则需要自定义存放路径。
        默认下载的是无后缀的文件,根据index区分,icon需要增加.png后缀,市场图增加.jpg后缀
        :param request:
        :param response:
        :param info:
        :return:
        '''
        item = request.meta['item']
        index = request.meta['index']
        today = str(datetime.date.today())
        # 定义在FILES_STORE下的存放路径为YYYY/MM/dd/app名称,如2019/11/28/和平精英
        outDir = today[0:4] + r"\\" + today[5:7] + r"\\" + today[8:] + r"\\" + item['name'] + r"\\"
        if index > 0:
            # index>0为市场图 命名为[index].jpg  注意:以数字命名的文件要转换成字符串,否则下载失败,不会报具体原因!!!
            file_name = outDir + str(index) + ".jpg"
        else:
            # index==0为icon下载,需采用png格式合适
            file_name = outDir + "icon.png"
        # 输出的文件已存在就删除
        if os.path.exists(FILES_STORE + outDir) and os.path.exists(FILES_STORE + file_name):
            os.remove(FILES_STORE + file_name)
        return file_name

    def item_completed(self, results, item, info):
        '''
        处理请求结果
        :param results:
        :param item:
        :param info:
        :return:
        '''
        '''
        results的格式为:
        [(True,
            {'checksum': '2b00042f7481c7b056c4b410d28f33cf',
            'path': 'full/7d97e98f8af710c7e7fe703abc8f639e0ee507c4.jpg',
            'url': 'http://www.example.com/images/product1.jpg'}),
        (True,
            {'checksum': 'b9628c4ab9b595f72f280b90c4fd093d',
            'path': 'full/1ca5879492b8fd606df1964ea3c1e2f4520f076f.jpg',
            'url': 'http://www.example.com/images/product2.jpg'}),
        (False,
            Failure(...))
        ]
        '''
        file_paths = [x['path'] for ok, x in results if ok]
        if not file_paths:
            raise DropItem("Item contains no files")

        for file_path in file_paths:
            if file_path.endswith("png"):
                # icon的图片地址赋值给iconPath
                item['iconPath'] = FILES_STORE + file_path
            else:
                # 市场图的地址给imagePaths 不存在属性就创建空数组
                if 'imagePaths' not in item:
                    item['imagePaths'] = []
                item['imagePaths'].append(FILES_STORE + file_path)
        return item

数据库存储

连接mysql采用的PyMySQL==0.9.2,小编新建了一个工具类存放,插入、更新、删除语句调用update(self, sql),查询语句调用query(self, sql)

class MySQLHelper:
    def __init__(self):
        pass

    def query(self, sql):
        # 打开数据库连接
        db = self.conn()

        # 使用cursor()方法获取操作游标
        cur = db.cursor()

        # 1.查询操作
        # 编写sql 查询语句  user 对应我的表名
        # sql = "select * from user"
        try:
            cur.execute(sql)  # 执行sql语句

            results = cur.fetchall()  # 获取查询的所有记录
            return results
        except Exception as e:
            thread_logger.debug('[mysql]:{} \n\tError SQL: {}'.format(e, sql))
            raise e
        finally:
            self.close(db)  # 关闭连接

    def update(self, sql):
        # 2.插入操作
        db = self.conn()

        # 使用cursor()方法获取操作游标
        cur = db.cursor()

        try:
            data = cur.execute(sql)
            # 提交
            data1 = db.commit()
            return True
        except Exception as e:
            thread_logger.debug('[mysql]:{} \n\tError SQL: {}'.format(e, sql))
            # 错误回滚
            db.rollback()
            return False
        finally:
            self.close(db)

    # 建立链接
    def conn(self):
        db = pymysql.connect(host="192.168.20.202", user="***",
                             password="****", db="app_anzhigame", port=3306, use_unicode=True, charset="utf8mb4")
        return db

    # 关闭
    def close(self, db):
        db.close()

更改AnzhispiderPipeline,插入数据,部分数据有默认值处理,

class AnzhispiderPipeline(object):
    """
    数据库存储
    """

    def __init__(self):
        # 打开数据库链接
        self.mysqlHelper = MySQLHelper()

    def process_item(self, item, spider):
        # 数据库存储的sql
        sql = "INSERT INTO games(link,name,versionCode,icon,type,onlineTime,size,download,author,intro,updateInfo,highlight,image1,image2,image3,image4,image5) " \
              "VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')" % (
                  item['link'], item['name'], parseProperty(item, "versionCode", "v1.0"),
                  parseProperty(item, "iconPath", ""), parseProperty(item, "type", ""),
                  parseProperty(item, "onlineTime", ""), parseProperty(item, "size", "0B"),
                  parseProperty(item, "download", "0"), parseProperty(item, "author", "未知"),
                  parseProperty(item, "intro", "无"), parseProperty(item, "updateInfo", "无"),
                  parseProperty(item, "highlight", "无"), parseImageList(item, 0), parseImageList(item, 1),
                  parseImageList(item, 2), parseImageList(item, 3), parseImageList(item, 4))
        # 插入数据
        self.mysqlHelper.update(sql)
        return item

def parseProperty(item, property, defaultValue)为自定义的方法,用于判空获取默认值,def parseImageList(item, index)用于获取市场图,

def parseProperty(item, property, defaultValue):
    """
    判断对象的对应属性是否为空 为空就返回默认值
    :param item: 对象
    :param property: 属性名称
    :param defaultValue: 默认值
    """
    if property in item and item[property]:
        return item[property]
    else:
        return defaultValue


def parseImageList(item, index):
    """
    返回市场图地址
    :param item:
    :param index:
    :return:
    """
    if "imagePaths" in item and item["imagePaths"]:
        # 有图片
        # 获取数组大小
        if len(item["imagePaths"]) >= index + 1:
            return item["imagePaths"][index]
        else:
            return ""
    else:
        return ""

配置settings.py

注意增加FILES_STORE用于存储文件下载的路径,MEDIA_ALLOW_REDIRECTS为允许图片重定向,因为安智的图片链接为重定向的,不设置会下载失败。

# 文件下载地址
FILES_STORE = ".\\anzhigames\\"

# 是否允许重定向(可选)
MEDIA_ALLOW_REDIRECTS = True

配置pipelines,注意ImageResPipeline的数值需要比AnzhispiderPipeline小,数值范围为0-1000,越小优先级越高。

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'anzhispider.pipelines.AnzhispiderPipeline': 300,
   'anzhispider.pipelines.ImageResPipeline': 11,
}

至此。结束。scrapy crawl AnzhiSpider运行,收工。项目下.\\anzhigames\\生成了图片,

Scrapy-爬取安智市场app详情

数据库存储情况

Scrapy-爬取安智市场app详情

需要项目源码,点击原文链接

? 更多好文欢迎关注我的公众号~

Scrapy-爬取安智市场app详情

点赞
收藏
评论区
推荐文章
python爬虫增加多线程获取数据
Python爬虫应用领域广泛,并且在数据爬取领域处于霸主位置,并且拥有很多性能好的框架,像Scrapy、Request、BeautifuSoap、urlib等框架可以实现爬行自如的功能,只要有能爬取的数据,Python爬虫均可实现。数据信息采集离不开Pyt
Karen110 Karen110
4年前
使用Scrapy网络爬虫框架小试牛刀
前言这次咱们来玩一个在Python中很牛叉的爬虫框架——Scrapy。scrapy介绍标准介绍Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的
Stella981 Stella981
4年前
Crawlscrapy分布式爬虫
1.概念:多台机器上可以执行同一个爬虫程序,实现网站数据的分布爬取2.原生的scrapy是不可以实现分布式式爬虫  a)调度器无法共享  b)管道无法共享3.scrapyredis组件:专门为scrapy开发的一套组件,该组件可以让scrapy实现分布式  a)pipinstallscrapyredis4.分布式爬取的流程:
Stella981 Stella981
4年前
Scrapy框架
\TOC\1\.Scrapy介绍1.1.Scrapy框架Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。!(http://ccszt.com.cn/python/%E7%88%AC%E8%99%AB/file/images/
Wesley13 Wesley13
4年前
(原创)Scrapy爬取美女图片续集
      上一篇咱们讲解了Scrapy的工作机制和如何使用Scrapy爬取美女图片,而今天接着讲解Scrapy爬取美女图片,不过采取了不同的方式和代码实现,对Scrapy的功能进行更深入的运用。!(https://oscimg.oschina.net/oscnet/495475f784c4eb6eadac4fb32e103808c26.jpg)
Stella981 Stella981
4年前
Scrapy爬取遇到的一点点问题
学了大概一个月Scrapy,自己写了些东东,遇到很多问题,这几天心情也不大好,小媳妇人也不舒服,休假了,自己研究了很久,有些眉目了利用scrapy框架爬取慕课网的一些信息步骤一:新建项目  scrapystartprojectmuke  进入muke   scrapygenspidermukewangimooc.co
Stella981 Stella981
4年前
Scrapy_redis
简介scrapy\_redis是一个基于Redis的Scrapy组件,用于scrapy项目的分布式部署和开发你可以启动多个spider对象,互相之间共享有一个redis的request队列,最适合多个域名的广泛内容的爬取特点分布式爬取分布式数据处理爬取到的item数据被推送到redis中,这意味着你可以启动尽可能多的item处理程序
Stella981 Stella981
4年前
AI 科学家带你快速 Get 人工智能最热技术
!(https://pic3.zhimg.com/80/v2af9f6637b50b09be60b00a42f3812d5e_1440w.jpg)日前,京东智联云与贪心学院联合举办的人工智能前沿技
Stella981 Stella981
4年前
30天如何实现超级APP从0到1开发?京东mPaaS EMOP最佳示范来了
!(https://pic3.zhimg.com/80/v2af9f6637b50b09be60b00a42f3812d5e_1440w.jpg)当前,随着移动通信市场的成熟,企业提供服务的商业模式也随之改变,几乎每一个涉足移动互联网的企业都要拥有一款自己的APP。在此过程中,企业如何解决用户体验、质量把控、业务验证和研发速度等方面的挑战,对于快速
Scrapy爬虫:利用代理服务器爬取热门网站数据
在当今数字化时代,互联网上充斥着大量宝贵的数据资源,而爬虫技术作为一种高效获取网络数据的方式,受到了广泛的关注和应用。本文将介绍如何使用Scrapy爬虫框架,结合代理服务器,实现对热门网站数据的高效爬取,以抖音为案例进行说明。1.简介Scrapy是一个强大
小白学大数据 小白学大数据
6个月前
Scrapy 框架实战:构建高效的快看漫画分布式爬虫
一、Scrapy框架概述Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,它提供了强大的数据提取能力、灵活的扩展机制以及高效的异步处理性能。其核心架构包括:●Engine:控制所有组件之间的数据流,当某个动作发生时触发事件●Schedul