经过上一章,爬虫的很多细节都已经了解,这一部分就比较简单了,实现一下断点续传。
基本思想就是数据库记录位置,使用MOngoDB的upsert方式对数据库更新,保证不重复的数据。太累了,懒得写了。
import random
from bs4 import BeautifulSoup
import requests
import time
import pymongo
from multiprocessing import Pool
# MogoDb设置
client = pymongo.MongoClient('localhost', 27017)
tongcheng_db = client["tongcheng"]
categories = tongcheng_db["categories"]
products = tongcheng_db["products"]
products.create_index("title", unique=True)
# 入口网址
start_url = 'http://sz.58.com/'
user_agents = '''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
...(为了不影响篇幅,省略大多数)
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'''
proxy_list = list() # 存储代理列表,防止过多访问代理网站
def get_response(url, use_proxy=True, num_retrials=4, timeout=5):
"""
获取url的响应。可以设置代理和重试次数
:param url: 要连接到网址
:param proxy: 代理
:param num_retrials: 重试次数
:return: 响应
"""
def random_ip():
"""
get random ip from an ip list
"""
def get_ips(url='http://haoip.cc/tiqu.htm', ips=proxy_list):
"""
find the ip list shown in url
"""
if len(proxy_list) != 0: # 如果proxy_list已经有值,直接返回即可
return ips
try:
response = requests.get(url, timeout=6)
response.raise_for_status()
except requests.HTTPError:
print('代理池服务器出现问题')
except requests.ReadTimeout:
print('请检查代理地址')
else:
soup = BeautifulSoup(response.text, 'lxml')
raw_ips = soup.select("div.col-xs-12")[0].text.replace(' ', '').split('\n')
for raw_ip in raw_ips:
if raw_ip == '':
continue
ips.append(raw_ip)
return ips
ips = get_ips()
random.seed()
if ips is None:
raise ValueError('代理异常')
ip = random.choice(ips)
return ip
def ramdom_headers():
"""
构造一个随机的Headers
"""
random.seed()
user_agent = random.choice(user_agents.split('\n'))
headers = {'User-Agent': user_agent}
return headers
if not use_proxy: # 如果不使用代理
try:
response = requests.get(url, headers=ramdom_headers(), timeout=timeout)
response.raise_for_status()
except: # 如果上面的代码执行报错
if num_retrials > 0: # num_retrials是重试次数
time.sleep(3) # 延迟3秒
print('获取网页出错,3S后将获取倒数第:', num_retrials, '次')
return get_response(url, False, num_retrials - 1) # 调用自身 并将次数减1
else:
print('开始使用代理')
return get_response(url, True) # 代理不为空的时候
else:
return response
#else: 如果使用代理
try:
ip = random_ip()
except ValueError as e:
print(str(e))
else:
proxy = {'http': ip} # 构造一个代理
try:
response = requests.get(url, headers=ramdom_headers(), proxies=proxy, timeout=timeout) # 使用代理获取response
except:
if num_retrials > 0:
time.sleep(3)
print('将会更换代理,3S后将重新获取倒数第', num_retrials, '次')
return get_response(url, True, num_retrials - 1)
else:
print('代理也不好使了!取消代理')
return get_response(url, False)
else:
print('当前代理是:', proxy)
return response
def get_categories(url, use_new=False):
"""
获得页面的类目链接
:param url: 页面链接
:param use_new: 是否重新爬取类目,即是否使用新数据库
:return: 链接网址
"""
if not use_new: # 如果不想重新爬取类目,检查时候数据库已有数据
if categories.count() != 0: # 如果已有数据,直接返回即可
return categories.find()
categories.drop() # 清空数据库
cats = set() # set类型,防止重复链接
r = get_response(url)
soup = BeautifulSoup(r.text, 'lxml')
links = soup.select('.colWrap em a')
for link in links:
cat_url = url + link.get('href').split('/')[1] # 构造链接
if cat_url == start_url: # 如果和入口网址一样,跳出本次循环
continue
if cat_url.endswith(".shtml"): # 如果以.shtml结尾,跳出本次循环
continue
cats.add(cat_url) # 加入set
for url in cats: # 将类目链接写入数据库
categories.insert_one({ "url": url, "page": 1, "isLast": False, "isStarted": False, "isFinished": False})
return categories.find() # 返回类目结果list,其中存放字典类型
def get_products_of_all_pages(page):
"""
获得一个类目内所有的商品信息,并写入数据库
:param page: 一个类目的第一个页面网址
"""
def get_products_of_one_page(url):
"""
或者一个类目某一个页面的商品信息,并写入数据库
:param url: 页面网址
"""
r = get_response(url)
soup = BeautifulSoup(r.text, 'lxml')
if soup.find('div', {"class": "noinfotishi"}): # 没有更多商品了
raise ValueError("没有了")
if not soup.find("td", {"class": "t"}): # 不是我要的商品
raise ValueError("不爬取这种")
items = soup.findAll("td", {"class": "t"})
for item in items:
try:
title = item.find("a").text
price = item.find("span", {"class": "price"}).text
location = item.find("span", {"class": "fl"}).text.replace('\n', '')
except AttributeError: # 如果上面信息有缺失,捕获这个错误
print(url + "有商品少了些信息")
continue
products.update({"title": title},
{"title": title, "price": price, "location": location},
upsert=True)
print("inserted")
print("ok")
time.sleep(1.5)
url = page["url"]
cat = categories.find({"url": url})[0]
page_num = cat["page"]
if cat["isLast"]:
return
while True:
categories.update({"url": url}, {"$set": {"isStarted": True, "page": page_num}})
try:
get_products_of_one_page(url + "/pn" + str(page_num))
print(page_num)
except ValueError as error: # 没有商品或者遇到错误,停止
print(str(error))
categories.update({"url": url}, {"$set": {"isLast": True}})
break
else:
categories.update({"url": url}, {"$set": {"isFinished": True}})
page_num += 1
def start():
cats = get_categories(start_url)
print(cats)
for cat in cats:
get_products_of_all_pages(cat)
start()