当当网简介
推荐搜索百度百科:当当网简介。
客户需求
本次爬取所内容为客户所需要的当当网全品类书籍中的小说,数据内容主要包括书籍名,和书籍售价,其他的根据客户需求进行更改。
数据量
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,但不是永久的封,可以使用代理的方式来绕开。
关于多线程的使用不再赘述。