今天看啥  ›  专栏  ›  梦想橡皮擦

情人节,你刚表白,而我已经开始选哪里拍婚纱照了~

梦想橡皮擦  · CSDN  ·  · 2021-02-12 14:15

梦想橡皮擦,一个逗趣的互联网高级网虫。

又到每年的 2 月 14 日了,最近这几天,你肯定会在博客上看到,程序员花式秀恩爱,但橡皮擦就不一样了,正在帮别人选婚纱照拍摄地。

当你 new 出来的对象问你,“北京在哪拍婚纱照便宜又好呀?” 你啪啪啪把数据展示出来,绝对可以赢得你的小可爱那爱恋的眼神。

写在前面

挖掘目的已经确定,下面就是挖掘代码编写的时间了,作为年轻人,好好秀恩爱吧,苦差事就交给我们这些过来人。

这次咱们的目标网站是: https://www.jiehun.com.cn/ ,遥想当年橡皮擦的婚纱照还是在婚博会上订的呢~

这个组织在每年春夏秋冬四季在北京、上海、广州、天津、武汉、杭州、成都等地同时举办大型结婚展。

目标页面长成下面这个样子。 情人节,你刚表白,而我已经开始选哪里拍婚纱照了~
我们要抓取的就是上面商铺的各种信息,包含商铺名,商铺地址,评星,点评数,价格。

数据抓取过程

在做正式抓取之前,可以先编写一个 demo,对数据进行简单的抓取与解析,具体实现可以参照下文。

其中用到了 XPath 解析,该网站如果不使用 UA 参数,无法获取到数据,也算是一种最简单的反爬手段吧。

def demo():
    headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"}
    url = f"https://www.jiehun.com.cn/beijing/ch2065/store-p13/?ce=beijing"
    content = r.get(url, headers=headers).text
    html = etree.HTML(content)
    li_list = html.xpath("//div[@id='stlist']/ul/li")
    for li in li_list:
        # 发现评分
        star = li.xpath("./div[@class='comment']/p[1]/b/text()")
        comment = "--"
        if star:
            # 评星
            star = star[0]
            comment = li.xpath("./div[@class='comment']/p[2]/a/text()")
            # 评论
            comment = comment[0]
        else:
            star = "--"

        # 店铺名称
        name = li.xpath(".//a[@class='namelimit']/text()")[0]
        # 地址
        store = li.xpath(
            ".//div[@class='storename']/following-sibling::p[1]/text()")[0]
        # 价钱
        price = li.xpath(
            ".//div[@class='storename']/following-sibling::p[2]/span[1]/text()")
        if price:
            price = li.xpath(
                ".//div[@class='storename']/following-sibling::p[2]/span[1]/text()")[0]
        else:
            price = "--"
        item = {
            "name": name,
            "store": store,
            "price": price,
            "star": star,
            "comment": comment
        }
        print(item)

        with open("hun.json", "ab+") as filename:
            filename.write(json.dumps(
                item, ensure_ascii=False).encode("utf-8") + b"\n")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

获取到的数据存储格式如下,以 JSON 格式存储,读取的时候每次读取一行即可。

{"name": "9Xi·婚纱摄影", "store": "商家地址:北京市朝阳区朝外大街丙10号9Xi结婚汇购物中心", "price": "¥4999", "star": "--", "comment": "--"}
{"name": "非目环球旅拍", "store": "商家地址:杭州市滨江区非目影像(总店)", "price": "¥19800", "star": "--", "comment": "--"}
{"name": "小白工作室(私人会所)", "store": "商家地址:朝阳北路天鹅湾北区7号楼二单元502(朝阳大悦城对面)", "price": "--", "star": "--", "comment": "--"}
{"name": "朵美婚拍", "store": "商家地址:北京市朝阳区广渠门外大街8号优士阁A座大堂底商", "price": "¥2999", "star": "--", "comment": "--"}
{"name": "柏悦时尚艺术馆", "store": "商家地址:立汤路186号龙德广场四层F420A", "price": "--", "star": "--", "comment": "--"}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

当测试数据抓取到之后,就可以对全北京的商铺(其他地区的修改对应地址即可)进行批量抓取了,本次数据量虽然不大,但是橡皮擦依旧为你贴心的准备了多线程爬虫(唉~没那么容易学习到爬虫技术)。

以下是完整代码部分,就为了你能获取到最全的数据,一次性的把代码都提供给你了,你的手指可以放在点赞按钮上,为橡皮擦点赞了。

import threading
import requests as r
from queue import Queue
import time
from lxml import etree
import json

CRAWL_EXIT = False
PARSE_EXIT = False


class ThreadCrawl(threading.Thread):
    def __init__(self, thread_name, page_queue, data_queue):
        super(ThreadCrawl, self).__init__()

        self.thread_name = thread_name
        self.page_queue = page_queue
        self.data_queue = data_queue
        self.headers = {
            "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"}

    def run(self):
        print("启动", self.thread_name)
        while not CRAWL_EXIT:
            try:
                #
                page = self.page_queue.get(False)
                url = f"https://www.jiehun.com.cn/beijing/ch2065/store-p{page}/?ce=beijing"

                content = r.get(url, headers=self.headers).text
                time.sleep(1)

                self.data_queue.put(content)

            except Exception as e:
                print(e)

        print("结束", self.thread_name)


class ThreadParse(threading.Thread):
    def __init__(self, thread_name, data_queue, filename, lock):
        super(ThreadParse, self).__init__()
        self.thread_name = thread_name
        self.data_queue = data_queue
        self.filename = filename
        self.lock = lock

    def run(self):
        print("启动", self.thread_name)
        while not PARSE_EXIT:
            try:
                html = self.data_queue.get(False)

                self.parse(html)

            except Exception as e:
                print(e)
        print("结束", self.thread_name)

    def parse(self, html):
        html = etree.HTML(html)

        li_list = html.xpath("//div[@id='stlist']/ul/li")
        for li in li_list:
            # 发现评分
            star = li.xpath("./div[@class='comment']/p[1]/b/text()")
            comment = "--"
            if star:
                # 评星
                star = star[0]
                comment = li.xpath("./div[@class='comment']/p[2]/a/text()")
                # 评论
                comment = comment[0]
            else:
                star = "--"

            # 店铺名称
            name = li.xpath(".//a[@class='namelimit']/text()")[0]
            # 地址
            store = li.xpath(
                ".//div[@class='storename']/following-sibling::p[1]/text()")[0]
            # 价钱
            price = li.xpath(
                ".//div[@class='storename']/following-sibling::p[2]/span[1]/text()")
            if price:
                price = li.xpath(
                    ".//div[@class='storename']/following-sibling::p[2]/span[1]/text()")[0]
            else:
                price = "--"

            item = {
                "name": name,
                "store": store,
                "price": price,
                "star": star,
                "comment": comment
            }

            with self.lock:
                self.filename.write(json.dumps(
                    item, ensure_ascii=False).encode("utf-8") + b"\n")


def main():
    # 页码
    page_queue = Queue(14)
    for i in range(1, 15):
        page_queue.put(i)

    # 数据挖掘结果
    data_queue = Queue()
    filename = open("hun.json", "ab+")

    # 锁
    lock = threading.Lock()

    # 三个挖掘线程
    crawl_list = ["挖掘线程1", "挖掘线程2", "挖掘线程3"]

    threadcrawl = []
    for thread_name in crawl_list:
        thread = ThreadCrawl(thread_name, page_queue, data_queue)
        thread.start()
        threadcrawl.append(thread)

    # 三个解析线程
    parse_list = ["解析线程1", "解析线程2", "解析线程3"]
    threadparse = []
    for thread_name in parse_list:
        thread = ThreadParse(thread_name, data_queue, filename, lock)
        thread.start()
        threadparse.append(thread)

    # 等待 page_queue 队列为空
    while not page_queue.empty():
        pass

    global CRAWL_EXIT
    CRAWL_EXIT = True

    print("page_queue为空")
    for thread in threadcrawl:
        thread.join()
        print("挖掘队列执行完毕")


    while not data_queue.empty():
        pass
    global PARSE_EXIT
    PARSE_EXIT = True

    for thread in threadparse:
        thread.join()
        print("解析队列执行完毕")

    with lock:
        filename.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158

本次数据获取,为了不让你那么容易就找到哪个商铺价钱便宜,我专门存储成了 JSON 格式,排序的工作就交给你自己来完成了。毕竟你不能有女朋友的同时,一点力也不出吧。
情人节,你刚表白,而我已经开始选哪里拍婚纱照了~

看图选照

你以为这样工作就做完了吗?当然没有,除了价格以外,咱们 new 出来的对象还需要看图选呢,至少要看看谁家拍摄技术高,更符合自己的调调。

接下来,再爬取一个: 目标地址

该网站页面数据如下:

情人节,你刚表白,而我已经开始选哪里拍婚纱照了~
要抓取的就是这几千张婚纱摄影照片~

该操作我将其分解成了两个步骤:

  • 第一个步骤批量采集图片详情页的地址;
  • 第二步针对详情页地址,获取图片。

核心部分代码修改如下:

抓取超链接部分,修改 XPath 解析地址即可。

def demo():
    headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"}
    url = f"https://www.jiehun.com.cn/beijing/ch2065/album-p461/?attr_110=&cate_id=2065&ce=beijing"
    content = r.get(url, headers=headers).text
    html = etree.HTML(content)
    a_list = html.xpath("//div[@class='rectangle_list']/ul/li/a/@href")

    for a in a_list:
        with open("album_link.json", "a+") as filename:
            filename.write(f"https://www.jiehun.com.cn/{a}\n")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

短暂运行一段时间后,得到超链接数据如下:

https://www.jiehun.com.cn//album/730704/
https://www.jiehun.com.cn//album/730703/
https://www.jiehun.com.cn//album/730702/
https://www.jiehun.com.cn//album/730701/
https://www.jiehun.com.cn//album/730700/
https://www.jiehun.com.cn//album/730699/
https://www.jiehun.com.cn//album/730698/
https://www.jiehun.com.cn//album/730697/
https://www.jiehun.com.cn//album/730696/
https://www.jiehun.com.cn//album/730695/
https://www.jiehun.com.cn//album/730694/
https://www.jiehun.com.cn//album/730693/
https://www.jiehun.com.cn//album/730679/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

图片抓取,利用 album_link.json 中存储的链接地址,解析对应页面中的 img 标签。

读取 album_link.json 中的数据,生成待抓取链接。

def read_file():
    page_queue = Queue()
    for f in open("album_link.json","r"):
        print(f.strip())
        page_queue.put(f)

    print(page_queue.qsize())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

提取对应 URL 中的图片地址,并保存。

def demo():
    headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"}
    url = f"https://www.jiehun.com.cn/album/730698/"
    content = r.get(url, headers=headers).text
    html = etree.HTML(content)
    title = html.xpath("//div[@class='detailintro_l']/h2/text()")[0]
    img_list = html.xpath("//div[@class='img']/img/@src")

    for index,img_url in enumerate(img_list):
        content = r.get(img_url, headers=headers).content
        with open(f"./imgs/{title}-{index}.jpg", "wb+") as filename:
            filename.write(content)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

编写好完整的代码之后,等着照片存储到本地,就可以给对象一张张的看了,看到好的,在查一下是哪个摄影店,去拍摄就好了。

情人节,你刚表白,而我已经开始选哪里拍婚纱照了~
代码运行一会,就 600+ 照片了,因为硬盘比较小,剩下的大家自己爬取吧

其实是被那一张张狂撒狗粮的照片,喂饱了~,ε=(´ο `*))) 唉,那一张张 KISS 照片,一直在给橡皮擦暴击。

情人节,你刚表白,而我已经开始选哪里拍婚纱照了~

写在后面

为了方便大家学习源码,我上传了一个收费源码: https://download.csdn.net/download/hihell/15222145 ,哈哈哈,如果你不想购买,添加橡皮擦微信索取吧。

情人节,你刚表白,而我已经开始选哪里拍婚纱照了~
相关阅读


技术专栏

  1. Python 爬虫 100 例教程,超棒的爬虫教程,立即订阅吧
  2. Python 爬虫小课,精彩 9 讲

今天是持续写作的第 79 / 100 天。
如果你有想要交流的想法、技术,欢迎在评论区留言。


如果你想跟博主建立亲密关系,可以关注博主公众号 “ 非本科程序员 ”,了解非本科程序员是如何成长。
博主 ID: 梦想橡皮擦 ,希望大家点赞、评论、收藏。




原文地址:访问原文地址
快照地址: 访问文章快照