万物科技 学,以致用

9. 断点续传

2017-04-03
Geng

经过上一章,爬虫的很多细节都已经了解,这一部分就比较简单了,实现一下断点续传。

基本思想就是数据库记录位置,使用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()

Comments

你可以请我喝喝茶,聊聊天,鼓励我

Wechat Pay
wechat

Thanks!