Python爬虫120例之第20例,1637、一路商机网全站加盟数据采集

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战

接下来的一些案例,将围绕销售用基础数据采集,行业将选择美妆行业,请知晓。

本案例将采用 lxmlcssselect 相结合的方式进行采集,重点在 cssselect 选择器。

目标站点分析

本次要抓取的目标为 http://www.1637.com/,该网站具备多分类,采集时提前将分类存储到一列表中,便于后续扩展。后来发现可一级行业可以选择 不限,此时可获取全部分类,基于此,我们先将全部数据抓取到本地,然后在筛选出美容/美妆行业相关加盟数据即可。

本次要抓取的数据量与页数如下图所示。

Python爬虫120例之第20例,1637、一路商机网全站加盟数据采集 抓取数据采用旧办法,先把 HTML 页面保存到本地,然后在进行二次处理。

使用到的技术点

请求数据使用 requests,数据提取使用 lxml + cssselect 实现,使用 cssselect 之前,通过 pip install cssselect 安装对应库即可。

安装完毕,在代码中有两种使用方式,其一采用 CSSSelector class,具体如下:

from lxml.cssselect import CSSSelector
# 与正则表达式的使用方式有点相似,先构造一个CSS选择器对象
sel = CSSSelector('#div_total>em', translator="html")
# 然后将 Element 对象传入
element = sel(etree.HTML(res.text))
print(element[0].text)
复制代码

上述用法适合提前构建好选择器,更便于扩展,如果不使用该方式,可以直接使用 cssselect method 进行实现,即下述代码:

# 通过 cssselect 选择器,选择 em 标签
div_total = element.cssselect('#div_total>em')
复制代码

不管使用上述两种方式中的哪一种,括号中的内容 #div_total>em 才是我们学习的重点,该写法是 CSS 选择器 的一种写法,如果你比较了解前端知识,很容易就可以掌握,如果不了解也没有问题,先记住如下内容。

CSS 选择器 假设存在如下一段 HTML 代码:

<div class="totalnum" id="div_total"><em>57041</em>个项目</div>
复制代码

其中 classid 都为 HTML 标签的属性值,一般 class 在网页中可以存在多个,而 id 只能存在一个。

如果希望获取 div 标签,使用 css 选择器,使用 #div_total 或者 .totalnum 都可以实现,重点注意如果依据 id 获取,那前面的符号为 #,如果依赖 class 获取,那前面的符号为 . 有的时候还会存在其它属性,在 css选择器 中,可以这样编写,修改 HTML 代码如下所示。

<div class="totalnum" id="div_total" custom="abc"> 共<em>57041</em>个项目 </div>
复制代码

编写如下测试代码,注意 CSSSelector 部分的 css选择器 写法,即 div[custom="abc"] em

sel = CSSSelector('div[custom="abc"] em', translator="html")
element = sel(etree.HTML('<div class="totalnum" id="div_total" custom="abc"> 共<em>57041</em>个项目 </div>'))
print(element[0].text)
复制代码

上述 css选择器 还应用到了一个知识点,叫做后代选择器,例如 #div_total>em,其中 #div_totalem 之间,存在一个 > 符号,该符号表示选择 id=div_total 的直接子元素 em,如果去除中间的 >,修改为 #div_total>em,表示选择 id=div_total 所有后代元素(子孙辈元素)中的 em 元素。

以上内容进行了简单掌握之后,你就可以简单编写自己的 cssselect 代码了。

编码时间

本案例采用的抓取方式为,先抓取 HTML 页面到本地,在针对本地文件进行解析,故采集代码比较简单,只需要动态获取一下总页码数即可。下述代码重点注意 get_pagesize 函数内部逻辑。

import requests
from lxml.html import etree
import random
import time


class SSS:
    def __init__(self):
        self.start_url = 'http://xiangmu.1637.com/p1.html'
        self.url_format = 'http://xiangmu.1637.com/p{}.html'
        self.session = requests.Session()
        self.headers = self.get_headers()

    def get_headers(self):
    	# 可从先前博客获取该函数
        uas = [
            "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
        ]
        ua = random.choice(uas)
        headers = {
            "user-agent": ua,
            "referer": "https://www.baidu.com"
        }
        return headers

    def get_pagesize(self):

        with self.session.get(url=self.start_url, headers=self.headers, timeout=5) as res:
            if res.text:
                element = etree.HTML(res.text)
                # 通过 cssselect 选择器,选择 em 标签
                div_total = element.cssselect('#div_total>em')
                # 获取 em 标签内部文本 div_total[0].text,并将其转换为整数
                total = int(div_total[0].text)
                # 获取页码
                pagesize = int(total / 10) + 1
                # print(pagesize)
                # 总数恰好被10整数,不用额外增加一页数据
                if total % 10 == 0:
                    pagesize = int(total / 10)

                return pagesize
            else:
                return None

    def get_detail(self, page):
        with self.session.get(url=self.url_format.format(page), headers=self.headers, timeout=5) as res:
            if res.text:
                with open(f"./加盟1/{page}.html", "w+", encoding="utf-8") as f:
                    f.write(res.text)
            else:
                # 如果无数据,重新请求
                print(f"页码{page}请求异常,重新请求")
                self.get_detail(page)

    def run(self):
        pagesize = self.get_pagesize()
        # 测试数据,可临时修改 pagesize = 20
        for page in range(1, pagesize):
            self.get_detail(page)
            time.sleep(2)
            print(f"页码{page}抓取完毕!")


if __name__ == '__main__':
    s = SSS()
    s.run()
复制代码

经过测试,如果不增加时间限制,很容易被限制 IP,即无法获取到数据,通过添加代理可以解决,如果只对数据感兴趣,可以直接在 下载地址 下载 HTML 包数据,解压密码为 cajie

Python爬虫120例之第20例,1637、一路商机网全站加盟数据采集

二次提取数据

当静态 HTML 全部爬取到本地之后,提取页面数据,就变得简单了,毕竟不需要再解决反爬问题。

此时用到的核心技术点就是读取文件,在通过 cssselect 提取固定数据值。

通过开发者工具,查询数据所在标签节点如下,针对 class='xminfo' 的内容进行提取即可。

Python爬虫120例之第20例,1637、一路商机网全站加盟数据采集

下述代码核心展示数据提取方法,其中 format 函数为重点学习内容,由于数据存储为 csv 文件,所以需要 remove_character 函数去处理 \n 和英文 , 号。

# 数据提取类
class Analysis:
    def __init__(self):
        pass

    # 去除特殊字符
    def remove_character(self, origin_str):
        if origin_str is None:
            return
        origin_str = origin_str.replace('\n', '')
        origin_str = origin_str.replace(',', ',')
        return origin_str

    def format(self, text):
        html = etree.HTML(text)
        # 获取所有项目区域 div
        div_xminfos = html.cssselect('div.xminfo')
        for xm in div_xminfos:
            adtexts = self.remove_character(xm.cssselect('a.adtxt')[0].text)  # 获取广告词列表
            url = xm.cssselect('a.adtxt')[0].attrib.get('href')  # 获取详情页地址

            brands = xm.cssselect(':nth-child(2)>:nth-child(2)')[1].text  # 获取品牌列表
            categorys = xm.cssselect(':nth-child(2)>:nth-child(3)>a')[0].text  # 获取分类,例如 ["餐饮","小吃"]
            types = ''
            try:
                # 此处可能不存在二级分类
                types = xm.cssselect(':nth-child(2)>:nth-child(3)>a')[1].text  # 获取分类,例如 ["餐饮","小吃"]
            except Exception as e:
                pass
            creation = xm.cssselect(':nth-child(2)>:nth-child(6)')[0].text  # 品牌建立时间列表
            franchise = xm.cssselect(':nth-child(2)>:nth-child(9)')[0].text  # 加盟店数量列表
            company = xm.cssselect(':nth-child(3)>span>a')[0].text  # 公司名称列表

            introduce = self.remove_character(xm.cssselect(':nth-child(4)>span')[0].text)  # 品牌介绍
            pros = self.remove_character(xm.cssselect(':nth-child(5)>:nth-child(2)')[0].text)  # 经营产品介绍
            investment = xm.cssselect(':nth-child(5)>:nth-child(4)>em')[0].text  # 投资金额
            # 拼接字符串
            long_str = f"{adtexts},{categorys},{types},{brands},{creation},{franchise},{company},{introduce},{pros},{investment},{url}"
            with open("./加盟数据.csv", "a+", encoding="utf-8") as f:
                f.write(long_str + "\n")

    def run(self):
        for i in range(1, 5704):
            with open(f"./加盟/{i}.html", "r", encoding="utf-8") as f:
                text = f.read()
                self.format(text)


if __name__ == '__main__':
    # 采集数据,运行哪部分,去除注释即可
    # s = SSS()
    # s.run()
    # 提取数据
    a = Analysis()
    a.run()
复制代码

上述代码在提取 HTML 标签时,反复用到了 :nth-child(2),该选择器是:匹配属于其父元素的第 N 个子元素,不论元素的类型,所以你只需要准确的找到元素位置即可。

收藏时间

代码下载地址:codechina.csdn.net/hihell/pyth…,可否给个 Star。

来都来了,不发个评论,点个赞,收个藏吗?

今天是持续写作的第 200 / 200 天。 可以关注我,点赞我、评论我、收藏我啦。

猜你喜欢

转载自juejin.im/post/7035181700506189855