关于爬取当当网书籍小说全品类的信息

当当网简介

推荐搜索百度百科:当当网简介。

客户需求

本次爬取所内容为客户所需要的当当网全品类书籍中的小说,数据内容主要包括书籍名,和书籍售价,其他的根据客户需求进行更改。

数据量

300W+左右(客户要求不低于100W+,需包含市面上的大部分书籍信息)

数据库

存储在MongoDB中。(根据实际情况可以适当更改数据库)

详细步骤:

一, 详细浏览网站结构

当当网的反爬措施比较简单,可以推荐新手进行练习。

找到需求所需的产品内容

由网页我们可以知道小说的分类共有这么多种,选择一个进入页面,直到找到需求所要的数据来源界面。

可以看到小说作品集下的中国分类共有数据60多万条数据。判断网页页面最多能查看多少条数据。

滑到页面底部可以看到最大页面为100页,每页的数据共有60条数据,所以该页面中的最大访问6000条数据。

所以需要继续细化分类条目, 尽量找到每个品类都有的分类,经过观察不难发现,每个页面都有关于价格和评分的分类,做出之间的规律。并且找到网页是如何进行翻页的,有的翻页只经过网页的跳转来实现的,有些是使用AJAX技术来实现的,每个技术实现的翻页,所对应的解决办法也有所不同,本网页属于前者,使用网页的跳转来进行页面的刷新。因此问题就迎刃而解了!

也可以使用不同分类来解决,思路:获取页面中所展示的总商品的数目,判断商品数是否大于6000,如果大于六千条数据继续进行细分,如果没有大于6000条数据直接利用页面,准备数据的爬取。

二, 数据爬取

经过对网页的分析,大体知道如何进行对数据的爬取,但在爬取的过程中要注意网页的反扒措施,在遇到反爬的时候如何进行处理,也非常的关键。

按照分析

从该网页进行数据的爬取:https://book.dangdang.com/01.03.htm

获取小说分类下的所有详细分类(也就是分类中的最后一个分类)

打开检查,点击网络,点击清除所有日志,再对该页面进行刷新。获取页面日志。(有些网站如果不是第一次打开该网站,可能网站中会有缓存数据, 建议选检查功能中的停用缓存,必要时可以通过开启无痕模式配合抓包工具进行使用)在检查的搜索框中搜索分类的其中之一。

由此可以找到相应的关于分类的链接,获取全部分类的链接

代码如下:

import requests
from lxml import etree

cookies = {
    'ddscreen': '2',
    '__permanent_id': '20240604151815693134497678848427367',
    '__visit_id': '20240604151815742869975903601645094',
    '__out_refer': '',
    'dest_area': 'country_id%3D9000%26province_id%3D111%26city_id%3D0%26district_id%3D0%26town_id%3D0',
    '__rpm': 's_605253.4516872..1717486590400%7Cs_605253.4516872..1717486809126',
    'search_passback': '588d03841aca6ac9d9c45e66000000003091c800b7c45e66',
    '__trace_id': '20240604154010309107329275302816450',
    'pos_9_end': '1717486810530',
    'pos_6_start': '1717486810752',
    'pos_6_end': '1717486810761',
    'ad_ids': '3225883%2C3225762%2C3225951%2C3272895%2C36685665%2C71063125%2C71063054%2C71062968%2C71062814%2C71062770%2C71062720%2C71062619%2C71062367%2C71062306%2C51019624%2C51019467%2C47359550%2C36039554%2C36038480%2C36038297%2C2533482%2C2533485%7C%233%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C1',
}

headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    # 'Cookie': 'ddscreen=2; __permanent_id=20240604151815693134497678848427367; __visit_id=20240604151815742869975903601645094; __out_refer=; dest_area=country_id%3D9000%26province_id%3D111%26city_id%3D0%26district_id%3D0%26town_id%3D0; __rpm=s_605253.4516872..1717486590400%7Cs_605253.4516872..1717486809126; search_passback=588d03841aca6ac9d9c45e66000000003091c800b7c45e66; __trace_id=20240604154010309107329275302816450; pos_9_end=1717486810530; pos_6_start=1717486810752; pos_6_end=1717486810761; ad_ids=3225883%2C3225762%2C3225951%2C3272895%2C36685665%2C71063125%2C71063054%2C71062968%2C71062814%2C71062770%2C71062720%2C71062619%2C71062367%2C71062306%2C51019624%2C51019467%2C47359550%2C36039554%2C36038480%2C36038297%2C2533482%2C2533485%7C%233%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C1',
    'Referer': 'https://category.dangdang.com/cp01.03.00.00.00.00.html',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
    'sec-ch-ua': '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
}

response = requests.get('https://category.dangdang.com/cp01.03.00.00.00.00.html', cookies=cookies, headers=headers)
html = etree.HTML(response.text)
url_list = []
url_lists = html.xpath("//li[@class='child_li '][1]//div[@class='clearfix']/span/a")
for url in url_lists:
    url_name = url.xpath("./text()")[0]
    url_href = url.xpath("./@href")[0][1:-5]
    url_list.append("-"+url_href)

print(url_list)

将链接地址保存在列表中,为接下来进行多线程提供方便

根据上面的分析爬取代码如下:

# 创建Redis连接
r = redis.Redis()

# 创建MongoDB数据库连接
client = pymongo.MongoClient()
db = client['dangdang']
collection = db['books']

url_class = ['-cp01.03.45.00.00.00',
             '-cp01.03.39.00.00.00', '-cp01.03.55.00.00.00', '-cp01.03.40.00.00.00', '-cp01.03.31.00.00.00',
             '-cp01.03.48.00.00.00', '-cp01.03.43.00.00.00', '-cp01.03.49.00.00.00', '-cp01.03.52.00.00.00',
             '-cp01.03.46.00.00.00', '-cp01.03.47.00.00.00', '-cp01.03.90.00.00.00', '-cp01.03.30.00.00.00',
             '-cp01.03.38.00.00.00', '-cp01.03.35.00.00.00', '-cp01.03.41.00.00.00',
             '-cp01.03.51.00.00.00', '-cp01.03.44.00.00.00', '-cp01.03.56.00.00.00', '-cp01.03.50.00.00.00',
             '-cp01.03.32.00.00.00', '-cp01.03.42.00.00.00', '-cp01.03.33.00.00.00', '-cp01.03.34.00.00.00']
prices = ["-lp0-hp9", "-lp9-hp19", "-lp19-hp29", "-lp29-hp39", "-lp39-hp"]
stars = ["-sc5", '-sc4', '-sc3', '-sc2', '-sc1']


def get_book_info():
    while not queue.empty():
        url_class_dict = queue.get()
        for price in prices:
            for star in stars:
                get_total_url = f'https://category.dangdang.com/pg1{url_class_dict}{price}{star}.html'
                headers = {
                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
                    'Accept-Language': 'zh-CN,zh;q=0.9',
                    'Cache-Control': 'max-age=0',
                    'Connection': 'keep-alive',
                    # 'Cookie': 'ddscreen=2; __permanent_id=20240604151815693134497678848427367; __visit_id=20240604151815742869975903601645094; __out_refer=; dest_area=country_id%3D9000%26province_id%3D111%26city_id%3D0%26district_id%3D0%26town_id%3D0; __rpm=s_605253.4516872..1717486590400%7Cs_605253.4516872..1717486809126; search_passback=588d03841aca6ac9d9c45e66000000003091c800b7c45e66; __trace_id=20240604154010309107329275302816450; pos_9_end=1717486810530; pos_6_start=1717486810752; pos_6_end=1717486810761; ad_ids=3225883%2C3225762%2C3225951%2C3272895%2C36685665%2C71063125%2C71063054%2C71062968%2C71062814%2C71062770%2C71062720%2C71062619%2C71062367%2C71062306%2C51019624%2C51019467%2C47359550%2C36039554%2C36038480%2C36038297%2C2533482%2C2533485%7C%233%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C3%2C1',
                    'Sec-Fetch-Dest': 'document',
                    'Sec-Fetch-Mode': 'navigate',
                    'Sec-Fetch-Site': 'same-origin',
                    'Sec-Fetch-User': '?1',
                    'Upgrade-Insecure-Requests': '1',
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
                    'sec-ch-ua': '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
                    'sec-ch-ua-mobile': '?0',
                    'sec-ch-ua-platform': '"Windows"',
                }

                if not r.sismember('get_total_url', get_total_url):
                    response = requests.get(get_total_url, headers=headers, proxies=proxies)
                    html = etree.HTML(response.text)
                    total = int(html.xpath("//em[@class='b']/text()")[0])
                    total = total / 60
                    # 向上取整
                    total = math.ceil(total)
                    for page in range(1, total + 1):
                        url_info = f'https://category.dangdang.com/pg{page}{url_class_dict}{price}{star}.html'
                        if not r.sismember('book_url', url_info):
                            response = requests.get(url_info, headers=headers, proxies=proxies)
                            html = etree.HTML(response.text)
                            books = html.xpath("//ul[@id='component_59']/li")
                            for book in books:
                                book_name = book.xpath(".//p[@class='name']/a/text()")
                                book_price = book.xpath(".//p[@class='price']/span[@class='search_now_price']/text()")[
                                    0]
                                book_data = {'book_name': book_name,
                                             'book_price': book_price
                                             }
                                hash_value = hashlib.md5(str(book_data).encode('utf-8')).hexdigest()
                                if not r.sismember('book_list', hash_value):

                                    # 写入数据库
                                    r.sadd('book_list', hash_value)
                                    collection.insert_one(book_data)
                                    print("加入新数据:", book_data)
                                else:
                                    pass
                            # 记录爬取的地址
                            r.sadd('book_url', url_info)
                        else:
                            print("已爬取:", url_info)
                            pass

                    r.sadd('get_total_url', get_total_url)
                else:
                    print("已爬取:", get_total_url)
                    pass


if __name__ == '__main__':
    queue = Queue()

    for url in url_class:
        queue.put(url)

    threads = []
    for i in range(100):
        t = Thread(target=get_book_info)
        threads.append(t)
        t.start()

    for i in threads:
        i.join()

将价位设置和评分也用列表进行表示

通过多重的for循环进行数据的依次遍历

三,数据的处理

请求下来的是一个html的text,要从中获取有用的信息,可有多种方法,正则,etree,等等。

在此使用的是etree加xpath,该方法适合绝大多数。

部分代码示例:

html = etree.HTML(response.text)
books = html.xpath("//ul[@id='component_59']/li")

在爬取的过程中,可能会有重复的数据,因此还需要进行数据的去重

四,数据去重

在数据爬取的过程中难免会有一些数据的重复,就要对数据进行去重的处理。redis的集合对此比较友好,可以自动去重,但是数据量过大,既要讲数据插入MongoDB中又要插入到redis中会造成资源的浪费,使用哈希算法就能很好的解决这个问题,将传入到redis中的数据进行哈希加密,不仅能保存数据,在redis的数据库中只保存固定较少的字节,就可一个,并且数据一样的经过哈希加密所体现的加密字节也是一样的。这大大保障了去重的准确性。

五,类似与断点重爬的实现

在进行数据的爬取测试过程中,程序会因为各种原因程序异常中断,重新执行会造成请求的浪费,也大大增加了反爬措施的执行,所以在进行数据爬取的过程中,增加另一个redis数据库的存储,在每次进行数据爬取的过程中,首先会判断爬取链接是否在数据库中,如果在就执行下面链接的爬取。如果没有在数据库中,则进行数据的爬取,当数据爬取完成后,将该链接加入数据库中。以这种方式程序的请求将大大减少,也增加了爬虫的效率。详细实现情况在代码中有所体现。

六,关于反反爬措施

该网站的反爬措施较少,请求次数过于频繁,会进行封一段时间的IP,但不是永久的封,可以使用代理的方式来绕开。

关于多线程的使用不再赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值