Использование фреймворка Scrapy Сканирование Scrapy Sina Weibo

рептилия MongoDB Scrapy Ajax

Основное использование каждого модуля в Scrapy, а также пула прокси и пула файлов cookie объяснялось ранее. Далее, давайте возьмем веб-сайт с сильной защитой от сканирования Sina Weibo в качестве примера, чтобы реализовать крупномасштабное сканирование Scrapy.

1. Цель этого раздела

Целью этого сканирования является общедоступная базовая информация о пользователях Sina Weibo, такая как псевдонимы пользователей, аватары, внимание пользователей, списки поклонников, опубликованные Weibo и т. д. Эта информация собирается и сохраняется в MongoDB.

2. Подготовка

Убедитесь, что пул прокси-серверов и пул файлов cookie, упомянутые выше, реализованы и работают нормально, а также установите библиотеки Scrapy и PyMongo.

Три, ползающие идеи

В первую очередь нам нужно добиться масштабного обхода пользователей. Принятый здесь метод сканирования состоит в том, чтобы начать с нескольких крупных V Weibo, сканировать их соответствующих поклонников и списки подписчиков, а затем получить поклонников и подписаться на списки поклонников и списки подписчиков и т. д. Реализовать рекурсивное сканирование. Если пользователь связан с другими пользователями в социальных сетях, их информация будет просканирована сканером, чтобы мы могли просканировать всех пользователей. Таким образом, мы можем получить уникальный идентификатор пользователя, а затем получить Weibo, опубликованный каждым пользователем в соответствии с идентификатором.

В-четвертых, анализ сканирования

Здесь мы выбираем сайт для сканирования: https://m.weibo.cn, этот сайт является сайтом мобильного терминала Weibo. При открытии сайта вы перейдете на страницу входа, потому что домашняя страница имеет ограничения входа. Тем не менее, мы можем обойти ограничения входа и напрямую открыть страницу сведений о пользователе, такую ​​как Weibo Чжоу Дунъюя, ссылка: https://m.weibo.cn/u/1916655407, вы можете войти на страницу его личных данных, как показано ниже показано.

Мы можем видеть внимание Чжоу Дунъюя и количество поклонников в верхней части страницы. Мы нажимаем «Подписаться», чтобы войти в ее список подписчиков, как показано на рисунке ниже.

Мы открываем инструмент разработчика, переключаемся на фильтр XHR и продолжаем раскрывать список наблюдения, и мы видим, что внизу будет много запросов Ajax.Эти запросы представляют собой запросы Ajax для получения списка наблюдения Чжоу Дунъюя, как показано на следующем рисунке. .

Открываем первый Ajax-запрос, его ссылка: https://m.weibo.cn/api/container/getIndex?containerid=231051

-_followers
-_1916655407&luicode=10000011&lfid=1005051916655407&featurecode=20000320&type=uid&value=1916655407&page=2, подробности показаны на следующем рисунке.

Тип запроса — тип GET, а возвращаемый результат — в формате JSON.После того, как мы расширим его, мы сможем увидеть основную информацию о пользователе, на которого он подписан. Далее нам просто нужно построить параметры этого запроса. Эта ссылка имеет всего 7 параметров, как показано на рисунке ниже.

Наиболее важным параметром являетсяcontaineridиpage. С этими двумя параметрами мы также можем получить результат запроса. Мы можем упростить интерфейс: https://m.weibo.cn/api/container/getIndex?containerid=231051.

-_followers
-_1916655407&page=2, здесьcontainer_idПервая половина является фиксированной, а вторая половина является идентификатором пользователя. Таким образом, параметры здесь могут быть построены, просто нужно изменитьcontainer_idПоследнийidиpageдля получения информации о списке наблюдения в разбивке на страницы.

Используя тот же метод, мы также можем проанализировать Ajax-ссылку сведений о пользователе и Ajax-ссылку списка пользователей Weibo, как показано ниже:

# 用户详情API
user_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&value={uid}&containerid=100505{uid}'
# 关注列表API
follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}'
# 粉丝列表API
fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&page={page}'
# 微博列表API
weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&page={page}&containerid=107603{uid}'

здесьuidиpageПредставляют идентификатор пользователя и номер страницы разбивки соответственно.

Обратите внимание, что этот API может измениться со временем или с новой версией Weibo, в зависимости от фактического измерения.

Мы начинаем сканировать несколько крупных V, сканируем их поклонников, списки наблюдения и информацию Weibo, а затем рекурсивно сканируем их поклонников и подписчиков, их списки наблюдения, списки наблюдения и информацию Weibo, рекурсивно сканируем и, наконец, сохраняем информацию Weibo. блоггеры, списки подписчиков и поклонников, а также опубликованные Weibo.

Мы выбираем MongoDB в качестве базы данных для хранения, которая может более удобно хранить фанатов пользователей и списки подписчиков.

5. Новые проекты

Затем мы используем Scrapy для реализации этого процесса сканирования. Сначала создайте проект, команда выглядит так:

scrapy startproject weibo

Войдите в проект, создайте нового паука с именем weibocn, и команда будет следующей:

scrapy genspider weibocn m.weibo.cn

Сначала мы модифицируем Spider, настроим URL каждого Ajax, выберем несколько больших V, назначим их ID в список и реализуемstart_requests()методом, то есть захватите личные данные каждого большого V по очереди, а затем используйтеparse_user()разобрать следующим образом:

from scrapy import Request, Spider

class WeiboSpider(Spider):
    name = 'weibocn'
    allowed_domains = ['m.weibo.cn']
    user_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&value={uid}&containerid=100505{uid}'
    follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}'
    fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&page={page}'
    weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&page={page}&containerid=107603{uid}'
    start_users = ['3217179555', '1742566624', '2282991915', '1288739185', '3952070245', '5878659096']

    def start_requests(self):
        for uid in self.start_users:
            yield Request(self.user_url.format(uid=uid), callback=self.parse_user)

    def parse_user(self, response):
        self.logger.debug(response)

6. Создать элемент

Далее мы анализируем основную информацию о пользователе и генерируем Item. Здесь мы сначала определяем несколько элементов, таких как пользователи, отношения пользователей и элементы Weibo, как показано ниже:

from scrapy import Item, Field

class UserItem(Item):
    collection = 'users'
    id = Field()
    name = Field()
    avatar = Field()
    cover = Field()
    gender = Field()
    description = Field()
    fans_count = Field()
    follows_count = Field()
    weibos_count = Field()
    verified = Field()
    verified_reason = Field()
    verified_type = Field()
    follows = Field()
    fans = Field()
    crawled_at = Field()

class UserRelationItem(Item):
    collection = 'users'
    id = Field()
    follows = Field()
    fans = Field()

class WeiboItem(Item):
    collection = 'weibos'
    id = Field()
    attitudes_count = Field()
    comments_count = Field()
    reposts_count = Field()
    picture = Field()
    pictures = Field()
    source = Field()
    text = Field()
    raw_text = Field()
    thumbnail = Field()
    user = Field()
    created_at = Field()
    crawled_at = Field()

определено здесьcollectionПоле, указывающее название сохраняемой Коллекции. Список подписчиков и поклонников пользователя напрямую определяется как единыйUserRelationItemidэто идентификатор пользователя,followsэто список наблюдения пользователя.fansЭто список подписчиков, но это не значит, что мы будем хранить списки подписчиков и подписчиков в отдельной коллекции. Позже мы будем использовать конвейер для обработки каждого элемента, его слияния и сохранения в пользовательской коллекции, поэтому элемент и коллекция не обязательно точно соответствуют друг другу.

7. Извлечь данные

Начинаем парсить основную информацию пользователя и реализуемparse_user()метод следующим образом:

def parse_user(self, response):
    """
    解析用户信息
    :param response: Response对象
    """
    result = json.loads(response.text)
    if result.get('data').get('userInfo'):
        user_info = result.get('data').get('userInfo')
        user_item = UserItem()
        field_map = {
            'id': 'id', 'name': 'screen_name', 'avatar': 'profile_image_url', 'cover': 'cover_image_phone',
            'gender': 'gender', 'description': 'description', 'fans_count': 'followers_count',
            'follows_count': 'follow_count', 'weibos_count': 'statuses_count', 'verified': 'verified',
            'verified_reason': 'verified_reason', 'verified_type': 'verified_type'
        }
        for field, attr in field_map.items():
            user_item[field] = user_info.get(attr)
        yield user_item
        # 关注
        uid = user_info.get('id')
        yield Request(self.follow_url.format(uid=uid, page=1), callback=self.parse_follows,
                      meta={'page': 1, 'uid': uid})
        # 粉丝
        yield Request(self.fan_url.format(uid=uid, page=1), callback=self.parse_fans,
                      meta={'page': 1, 'uid': uid})
        # 微博
        yield Request(self.weibo_url.format(uid=uid, page=1), callback=self.parse_weibos,
                      meta={'page': 1, 'uid': uid})

Здесь мы выполнили две операции в общей сложности.

  • Проанализируйте JSON, чтобы извлечь информацию о пользователе и сгенерировать UserItem для возврата. Мы не использовали традиционный метод присваивания «один за другим», а определили отношения сопоставления полей. Имя поля, которое мы определяем, может отличаться от имени поля пользователя в JSON, поэтому мы определяем его здесь как словарь, а затем просматриваем каждое поле словаря, чтобы выполнить назначение поля за полем.

  • Постройте связь внимания пользователя, поклонников и первой страницы Weibo и сгенерируйте Запрос, единственный требуемый параметр здесь — идентификатор пользователя. Кроме того, начальный номер страницы разбиения на страницы может быть напрямую установлен на 1.

Далее нам также нужно сохранить список подписчиков и подписчиков пользователя. Взяв в качестве примера список наблюдения, метод синтаксического анализа выглядит следующим образом.parse_follows(), реализация выглядит так:

def parse_follows(self, response):
    """
    解析用户关注
    :param response: Response对象
    """
    result = json.loads(response.text)
    if result.get('ok') and result.get('data').get('cards') and len(result.get('data').get('cards')) and result.get('data').get('cards')[-1].get(
        'card_group'):
        # 解析用户
        follows = result.get('data').get('cards')[-1].get('card_group')
        for follow in follows:
            if follow.get('user'):
                uid = follow.get('user').get('id')
                yield Request(self.user_url.format(uid=uid), callback=self.parse_user)
        # 关注列表
        uid = response.meta.get('uid')
        user_relation_item = UserRelationItem()
        follows = [{'id': follow.get('user').get('id'), 'name': follow.get('user').get('screen_name')} for follow in
                   follows]
        user_relation_item['id'] = uid
        user_relation_item['follows'] = follows
        user_relation_item['fans'] = []
        yield user_relation_item
        # 下一页关注
        page = response.meta.get('page') + 1
        yield Request(self.follow_url.format(uid=uid, page=page),
                      callback=self.parse_follows, meta={'page': page, 'uid': uid})

Итак, в этом методе мы делаем следующие три вещи.

  • Проанализируйте информацию о каждом пользователе в списке наблюдения и инициируйте новый запрос на анализ. Сначала мы анализируем информацию списка наблюдения, получаем идентификатор пользователя, а затем используемuser_urlСоздайте запрос для доступа к сведениям о пользователе, обратный вызов только что определенparse_user()метод.

  • Извлеките ключевую информацию из списка наблюдения пользователя и сгенерируйтеUserRelationItem.idПоле напрямую задается идентификатором пользователя, а информация о пользователе в возвращаемых данных JSON содержит много избыточных полей. Здесь мы извлекаем только идентификатор и имя пользователя соответствующего пользователя и назначаем ихfollowsполе,fansполе установлено в пустой список. Это создает список наблюдения с идентификаторами пользователей и пользовательскими частями.UserRelationItem, затем объедините и сохраните файлы с тем же идентификаторомUserRelationItemсписок последователей и последователей.

  • Извлеките следующую страницу, чтобы следовать. Просто добавьте 1 к номеру страницы этого запроса. Номер страницы пагинации по запросуmetaАтрибуты передаются, ответыmetaполучить. Таким образом мы создаем и возвращаем запрос на список наблюдения на следующей странице.

Принцип захвата списка подписчиков такой же, как и при захвате списка подписчиков, и здесь повторяться не будет.

Далее нам еще нужно реализовать метод, т.е.parse_weibos(), который используется для сбора информации пользователя Weibo, реализация выглядит следующим образом:

def parse_weibos(self, response):
    """
    解析微博列表
    :param response: Response对象
    """
    result = json.loads(response.text)
    if result.get('ok') and result.get('data').get('cards'):
        weibos = result.get('data').get('cards')
        for weibo in weibos:
            mblog = weibo.get('mblog')
            if mblog:
                weibo_item = WeiboItem()
                field_map = {
                    'id': 'id', 'attitudes_count': 'attitudes_count', 'comments_count': 'comments_count', 'created_at': 'created_at',
                    'reposts_count': 'reposts_count', 'picture': 'original_pic', 'pictures': 'pics',
                    'source': 'source', 'text': 'text', 'raw_text': 'raw_text', 'thumbnail': 'thumbnail_pic'
                }
                for field, attr in field_map.items():
                    weibo_item[field] = mblog.get(attr)
                weibo_item['user'] = response.meta.get('uid')
                yield weibo_item
        # 下一页微博
        uid = response.meta.get('uid')
        page = response.meta.get('page') + 1
        yield Request(self.weibo_url.format(uid=uid, page=page), callback=self.parse_weibos,
                      meta={'uid': uid, 'page': page})

это здесьparse_weibos()Методы решают две задачи.

  • Извлеките информацию пользователя Weibo и сгенерируйте WeiboItem. Здесь также создается таблица сопоставления полей для реализации массового назначения полей.

  • Извлеките список микроблогов на следующей странице. Здесь также необходимо передать идентификатор пользователя и номер страницы разбивки на страницы.

На данный момент проект Weibo Spider завершен. Позже необходимо очистить и сохранить данные, а также подключить пул прокси и пул Cookies для предотвращения антикраулеров.

Восемь, очистка данных

Время некоторых Weibo может не соответствовать стандартному времени, например, оно может отображаться как только что, несколько минут назад, несколько часов назад, вчера и т. д. Здесь нам нужно преобразовать эти времена равномерно, чтобы достичьparse_time()метод следующим образом:

def parse_time(self, date):
    if re.match('刚刚', date):
        date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time()))
    if re.match('\d+分钟前', date):
        minute = re.match('(\d+)', date).group(1)
        date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time() - float(minute) * 60))
    if re.match('\d+小时前', date):
        hour = re.match('(\d+)', date).group(1)
        date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time() - float(hour) * 60 * 60))
    if re.match('昨天.*', date):
        date = re.match('昨天(.*)', date).group(1).strip()
        date = time.strftime('%Y-%m-%d', time.localtime() - 24 * 60 * 60) + ' ' + date
    if re.match('\d{2}-\d{2}', date):
        date = time.strftime('%Y-', time.localtime()) + date + ' 00:00'
    return date

Мы используем регулярные выражения для извлечения некоторых ключевых чисел и используем библиотеку времени для преобразования стандартного времени.

Возьмем обработку X минут назад в качестве примера, время сканирования будет назначено какcreated_atполе. Сначала мы используем обычное совпадение для этого времени, выражение записывается\d+分钟前, если извлеченное время соответствует этому выражению, извлеките число, чтобы вы могли получить количество минут. Следующее использованиеtimeмодульныйstrftime()метод, первый параметр передается в формате времени, который необходимо преобразовать, а второй параметр — это метка времени. Здесь мы вычитаем это количество минут из текущей метки времени и умножаем на 60, чтобы получить метку времени в это время, чтобы мы могли получить правильное время после форматирования.

Затем Pipeline может реализовать следующую обработку:

class WeiboPipeline():
    def process_item(self, item, spider):
        if isinstance(item, WeiboItem):
            if item.get('created_at'):
                item['created_at'] = item['created_at'].strip()
                item['created_at'] = self.parse_time(item.get('created_at'))

Мы не поняли это правильно в Паукеcrawled_atНазначение поля, которое представляет время сканирования, мы можем назначить его текущему времени единообразно, реализация выглядит следующим образом:

class TimePipeline():
    def process_item(self, item, spider):
        if isinstance(item, UserItem) or isinstance(item, WeiboItem):
            now = time.strftime('%Y-%m-%d %H:%M', time.localtime())
            item['crawled_at'] = now
        return item

Здесь мы считаем, что если элемент относится к типу UserItem или WeiboItem, то дать емуcrawled_atПолю присваивается текущее время.

С помощью двух вышеупомянутых конвейеров мы завершили работу по очистке данных, которая в основном представляет собой преобразование времени.

9. Хранение данных

После очистки данных нам нужно сохранить данные в базе данных MongoDB. Здесь мы реализуем класс MongoPipeline следующим образом:

import pymongo

class MongoPipeline(object):
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DATABASE')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]
        self.db[UserItem.collection].create_index([('id', pymongo.ASCENDING)])
        self.db[WeiboItem.collection].create_index([('id', pymongo.ASCENDING)])

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        if isinstance(item, UserItem) or isinstance(item, WeiboItem):
            self.db[item.collection].update({'id': item.get('id')}, {'$set': item}, True)
        if isinstance(item, UserRelationItem):
            self.db[item.collection].update(
                {'id': item.get('id')},
                {'$addToSet':
                    {
                        'follows': {'$each': item['follows']},
                        'fans': {'$each': item['fans']}
                    }
                }, True)
        return item

Текущий MongoPipeline отличается от того, что мы писали ранее, в основном в следующих моментах.

  • open_spider()Индекс коллекции добавляется в метод, где индекс добавляется к обоим элементам, а индексированное полеid. Поскольку на этот раз мы сканируем в больших масштабах, процесс сканирования включает в себя обновление данных, поэтому мы создали индекс для каждой коллекции, что может значительно повысить эффективность поиска.

  • существуетprocess_item()метод хранится с использованиемupdate()метод, первый параметр — это условие запроса, а второй параметр — просканированный элемент. Здесь мы используем$setоператора, если обнаружены повторяющиеся данные, данные могут быть обновлены без удаления существующих полей. Если не добавить сюда$setоператор, то прямойitemЗаменить, что может привести к очистке существующих полей, таких как подписка и список подписчиков. Для третьего параметра установлено значение True, чтобы вставить данные, если они не существуют. Таким образом, мы можем обновлять данные, когда они существуют, и вставлять данные, когда они не существуют, чтобы получить эффект дедупликации.

  • Для пользовательских списков подписчиков и подписчиков мы используем новый оператор, называемый$addToSet, оператор, который вставляет данные в поле типа списка, удаляя дублирование. Его значением является имя поля, которым необходимо управлять. используется здесь$eachОператор просматривает данные списка, которые необходимо вставить, чтобы вставить данные о внимании пользователя или поклоннике в указанное поле один за другим. Для получения дополнительных сведений об этой операции см. официальную документацию MongoDB по ссылке: https://docs.mongodb.com/manual/reference/operator/update/addToSet/.

10. Подключение к пулу файлов cookie

Способность Sina Weibo к сканированию очень сильна, поэтому нам необходимо принять некоторые меры, чтобы предотвратить успешное сканирование данных антикраулерами.

Если вы не войдете в систему и напрямую не запросите интерфейс Weibo API, очень легко получить код состояния 403. Мы также упоминали об этой ситуации в разделе Пул файлов cookie. Итак, здесь мы реализуем промежуточное программное обеспечение, которое добавляет случайные файлы cookie к каждому запросу.

Сначала мы открываем пул файлов cookie, чтобы модуль API работал нормально. Например, запустите порт 5000 локально и посетите: http://localhost:5000/weibo/random, чтобы получить случайные файлы cookie. Конечно, пул файлов cookie также можно развернуть на удаленном сервере, чтобы изменить только ссылку, к которой осуществляется доступ.

Мы запускаем пул файлов cookie локально и реализуем промежуточное ПО следующим образом:

class CookiesMiddleware():
    def __init__(self, cookies_url):
        self.logger = logging.getLogger(__name__)
        self.cookies_url = cookies_url

    def get_random_cookies(self):
        try:
            response = requests.get(self.cookies_url)
            if response.status_code == 200:
                cookies = json.loads(response.text)
                return cookies
        except requests.ConnectionError:
            return False

    def process_request(self, request, spider):
        self.logger.debug('正在获取Cookies')
        cookies = self.get_random_cookies()
        if cookies:
            request.cookies = cookies
            self.logger.debug('使用Cookies ' + json.dumps(cookies))

    @classmethod
    def from_crawler(cls, crawler):
        settings = crawler.settings
        return cls(
            cookies_url=settings.get('COOKIES_URL')
        )

Мы сначала используемfrom_crawler()метод получилCOOKIES_URLПеременные, которые определены в settings.py, это интерфейс, о котором мы только что говорили. Реализовать следующееget_random_cookies()Этот метод в основном предназначен для запроса этого интерфейса пула файлов cookie и получения случайных файлов cookie, возвращаемых интерфейсом. В случае успешного получения вернуть Cookies; в противном случае вернутьFalse.

Далее, вprocess_request()метод, мы даемrequestобъектcookiesНазначение атрибута, его значением является полученный случайный файл cookie, поэтому мы успешно назначаем файлы cookie каждому запросу.

Если это ПО промежуточного слоя включено, каждому запросу будут назначены случайные файлы cookie. Таким образом, мы можем имитировать запрос после входа в систему, и код состояния 403 в основном не будет отображаться.

11. Стыковка пула прокси

Еще одна мера защиты от сканирования Weibo заключается в том, что код состояния 414 будет отображаться, когда он обнаружит, что тот же объем IP-запросов слишком велик. Если вы столкнулись с такой ситуацией, вы можете переключить прокси. Например, работая на локальном порту 5555, адрес для получения случайного доступного прокси: http://localhost:5555/random, и доступ к этому интерфейсу может получить случайный доступный прокси. Далее мы реализуем промежуточное ПО, код выглядит следующим образом:

class ProxyMiddleware():
    def __init__(self, proxy_url):
        self.logger = logging.getLogger(__name__)
        self.proxy_url = proxy_url

    def get_random_proxy(self):
        try:
            response = requests.get(self.proxy_url)
            if response.status_code == 200:
                proxy = response.text
                return proxy
        except requests.ConnectionError:
            return False

    def process_request(self, request, spider):
        if request.meta.get('retry_times'):
            proxy = self.get_random_proxy()
            if proxy:
                uri = 'https://{proxy}'.format(proxy=proxy)
                self.logger.debug('使用代理 ' + proxy)
                request.meta['proxy'] = uri

    @classmethod
    def from_crawler(cls, crawler):
        settings = crawler.settings
        return cls(
            proxy_url=settings.get('PROXY_URL')
        )

По такому же принципу реализуемget_random_proxy()Метод используется для запроса интерфейса пула прокси для получения случайного прокси. Если приобретение прошло успешно, вернитесь для смены прокси, в противном случае вернитесьFalse. существуетprocess_request()метод, мы даемrequestобъектmetaназначать свойствоproxyполе, значение которого является прокси.

Кроме того, условием суждения агента по назначению является текущееretry_timesОн не пустой, то есть прокси включается после неудачного первого запроса, потому что скорость доступа будет медленнее после использования прокси. Поэтому мы установили здесь, чтобы включить прокси только при повторной попытке, в противном случае запрашивать напрямую. Таким образом, можно гарантировать прямое сканирование без бана и гарантированную скорость сканирования.

12. Включить промежуточное ПО

Затем мы включаем эти два промежуточных ПО в файле конфигурации и модифицируем settings.py следующим образом:

DOWNLOADER_MIDDLEWARES = {
    'weibo.middlewares.CookiesMiddleware': 554,
    'weibo.middlewares.ProxyMiddleware': 555,
}

Обратите внимание на настройки приоритета здесь, Настройки промежуточного программного обеспечения загрузки Scrapy по умолчанию, упомянутые ранее, следующие:

{
    'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
    'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
    'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
    'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
    'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
    'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
    'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
    'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
    'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
    'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
}

Чтобы наше пользовательское CookiesMiddleware вступило в силу, оно вызывается перед встроенным CookiesMiddleware. Приоритет встроенного CookiesMiddleware равен 700, поэтому здесь мы можем установить число меньше 700.

Чтобы наш пользовательский ProxyMiddleware вступил в силу, он вызывается перед встроенным HttpProxyMiddleware. Приоритет встроенного HttpProxyMiddleware равен 750, поэтому здесь мы можем установить число меньше 750.

13. Операция

На данный момент реализован весь поисковый робот Weibo. Мы запускаем сканер, выполнив следующую команду:

scrapy crawl weibocn

Результат выглядит следующим образом:

2017-07-11 17:27:34 [urllib3.connectionpool] DEBUG: http://localhost:5000 "GET /weibo/random HTTP/1.1" 200 339
2017-07-11 17:27:34 [weibo.middlewares] DEBUG: 使用Cookies {"SCF": "AhzwTr_DxIGjgri_dt46_DoPzUqq-PSupu545JdozdHYJ7HyEb4pD3pe05VpbIpVyY1ciKRRWwUgojiO3jYwlBE.", "_T_WM": "8fe0bc1dad068d09b888d8177f1c1218", "SSOLoginState": "1501496388", "M_WEIBOCN_PARAMS": "uicode%3D20000174", "SUHB": "0tKqV4asxqYl4J", "SUB": "_2A250e3QUDeRhGeBM6VYX8y7NwjiIHXVXhBxcrDV6PUJbkdBeLXjckW2fUT8MWloekO4FCWVlIYJGJdGLnA.."}
2017-07-11 17:27:34 [weibocn] DEBUG: <200 https://m.weibo.cn/api/container/getIndex?uid=1742566624&type=uid&value=1742566624&containerid=1005051742566624>
2017-07-11 17:27:34 [scrapy.core.scraper] DEBUG: Scraped from <200 https://m.weibo.cn/api/container/getIndex?uid=1742566624&type=uid&value=1742566624&containerid=1005051742566624>
{'avatar': 'https://tva4.sinaimg.cn/crop.0.0.180.180.180/67dd74e0jw1e8qgp5bmzyj2050050aa8.jpg',
 'cover': 'https://tva3.sinaimg.cn/crop.0.0.640.640.640/6ce2240djw1e9oaqhwllzj20hs0hsdir.jpg',
 'crawled_at': '2017-07-11 17:27',
 'description': '成长,就是一个不断觉得以前的自己是个傻逼的过程',
 'fans_count': 19202906,
 'follows_count': 1599,
 'gender': 'm',
 'id': 1742566624,
 'name': '思想聚焦',
 'verified': True,
 'verified_reason': '微博知名博主,校导网编辑',
 'verified_type': 0,
 'weibos_count': 58393}

После запуска в течение определенного периода времени мы можем перейти к базе данных MongoDB для просмотра данных, а просканированные данные показаны на следующем рисунке.

Что касается информации о пользователе, мы не только сканируем его основную информацию, но также добавляем следующие и фан-списки вfollowsиfansполе и выполните операцию дедупликации. Для информации Weibo мы успешно выполнили преобразование времени, а также сохранили информацию о списке изображений Weibo.

14. Код в этом разделе

Кодовый адрес этого раздела: https://github.com/Python3WebSpider/Weibo.

15. Заключение

В этом разделе реализовано сканирование списков подписчиков Sina Weibo и их поклонников, а также информации Weibo, а также подключение пула файлов cookie и пула прокси-серверов для обработки антисканеров. Однако сейчас он сканирует для одной машины. Позже мы изменим этот проект на распределенный краулер, чтобы еще больше повысить эффективность сканирования.


Этот ресурс был впервые опубликован в личном блоге Цуй Цинцай Цзин Ми:Практическое руководство по разработке веб-краулера на Python3 | Цзин Ми

Если вы хотите узнать больше информации о поисковых роботах, обратите внимание на мой личный публичный аккаунт WeChat: Coder of Attack.

WeChat.QQ.com/Day/5 Это радость VE Z…(автоматическое распознавание QR-кода)