Научите, как быстро внедрить поисковый робот

задняя часть Python GitHub открытый источник рептилия Язык программирования поисковый движок
Научите, как быстро внедрить поисковый робот

Что такое рептилия?


Если вы никогда не контактировали с рептилиями, вы можете немного запутаться, что такое рептилия? На самом деле концепция краулера очень проста.В эпоху Интернета всемирная паутина уже является носителем большого количества информации.Как эффективно использовать и извлекать эту информацию является огромной проблемой.Когда мы отправляем запрос на веб-сайт с помощью браузера, сервер отвечаетHTMLТекст обрабатывается и отображается браузером. Сканер использует это, имитируя запрос пользователя через программу, чтобы получитьHTMLсодержание и извлечь из него необходимые данные и информацию. Если вы думаете о сети как о паутине, поисковые программы подобны паукам в паутине, постоянно сканирующим данные и информацию.

Концепция рептилий очень проста и понятна, используяpythonВстроенныйurllibЛюбая библиотека может реализовать простой сканер. Следующий код является очень простым сканером, если есть базовыеpythonЗнания должны быть поняты. он собирает все<a>теги (без вынесения каких-либо правил), а затем продолжить глубокий поиск по этим ссылкам.

from bs4 import BeautifulSoup
import urllib
import os
from datetime import datetime

# 网页的实体类,只含有两个属性,url和标题
class Page(object):
    def __init__(self,url,title):
        self._url = url
        self._title = title

    def __str__(self):
        return '[Url]: %s [Title]: %s' %(self._url,self._title)

    __repr__ = __str__

    @property
    def url(self):
        return self._url

    @property
    def title(self):
        return self._title

    @url.setter
    def url(self,value):
        if not isinstance(value,str):
            raise ValueError('url must be a string!')
        if value == '':
            raise ValueError('url must be not empty!')
        self._url = value

    @title.setter
    def title(self,value):
        if not isinstance(value,str):
            raise ValueError('title must be a string!')
        if value == '':
            raise ValueError('title must be not empty!')
        self._title = value

class Spider(object):

    def __init__(self,init_page):
        self._init_page = init_page # 种子网页,也就是爬虫的入口
        self._pages = []
        self._soup = None # BeautifulSoup 一个用来解析HTML的解析器

    def crawl(self):
        start_time = datetime.now()
        print('[Start Time]: %s' % start_time)
        start_timestamp = start_time.timestamp()
        tocrawl = [self._init_page] # 记录将要爬取的网页
        crawled = [] # 记录已经爬取过的网页
        # 不断循环,直到将这张图搜索完毕
        while tocrawl:
            page = tocrawl.pop()
            if page not in crawled:
                self._init_soup(page)
                self._packaging_to_pages(page)
                links = self._extract_links()
                self._union_list(tocrawl,links)
                crawled.append(page)
        self._write_to_curdir()
        end_time = datetime.now()
        print('[End Time]: %s' % end_time)
        end_timestamp = end_time.timestamp()
        print('[Total Time Consuming]: %f.3s' % (start_timestamp - end_timestamp) / 1000)

    def _init_soup(self,page):
        page_content = None
        try:
            # urllib可以模拟用户请求,获得响应的HTML文本内容
            page_content = urllib.request.urlopen(page).read()
        except:
            page_content = ''
        # 初始化BeautifulSoup,参数二是使用到的解析器名字    
        self._soup = BeautifulSoup(page_content,'lxml')

    def _extract_links(self):
        a_tags = self._soup.find_all('a') # 找到所有a标签
        links = []
        # 收集所有a标签中的链接
        for a_tag in a_tags:
            links.append(a_tag.get('href'))
        return links

    def _packaging_to_pages(self,page):
        title_string = ''
        try:
            title_string = self._soup.title.string # 获得title标签中的文本内容
        except AttributeError as e :
            print(e)
        page_obj = Page(page,title_string)
        print(page_obj)
        self._pages.append(page_obj)

    # 将爬取到的所有信息写入到当前目录下的out.txt文件
    def _write_to_curdir(self):
        cur_path = os.path.join(os.path.abspath('.'),'out.txt')
        print('Start write to %s' % cur_path)
        with open(cur_path,'w') as f:
            f.write(self._pages)

    # 将dest中的不存在于src的元素合并到src
    def _union_list(self,src,dest):
        for dest_val in dest:
            if dest_val not in src:
                src.append(dest_val)

    @property
    def init_page(self):
        return self._init_page

    @property
    def pages(self):
        return self._pages


def test():
    spider = Spider('https://sylvanassun.github.io/')
    spider.crawl()

if __name__ == '__main__':
    test()

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

Автор этой статьи:SylvanasSun(sylvanas.sun@gmail.com).Для перепечатки следующий абзац следует разместить в начале статьи (сохранить гиперссылку).
Эта статья была впервые опубликована сSylvanasSun Blog, оригинальная ссылка:sylva, то есть sun.GitHub.IO/2017/09/20/…

BeautifulSoup


BeautifulSoup — этоHTMLиXMLизвлекать данные изpythonбиблиотека. Beautiful Soup автоматически конвертирует входные документы в кодировку Unicode, а выходные документы — в кодировку utf-8. Вам не нужно учитывать метод кодирования, если только в документе не указан метод кодирования, тогда Beautiful Soup не сможет автоматически распознать метод кодирования. Затем вам просто нужно указать исходную кодировку.

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

BeautifulSoup3 прекратил техническое обслуживание, и теперь мы в основном используем BeautifulSoup4.Установить BeautifulSoup4 очень просто, просто выполните следующие команды.

pip install beautifulsoup4

затем изbs4Импортируйте объект BeautifulSoup в модуль и создайте этот объект.

from bs4 import BeautifulSoup

soup = BeautifulSoup(body,'lxml')

Создание объекта BeautifulSoup требует передачи двух параметров, первый из которых необходимо проанализировать.HTMLcontent, вторым параметром является имя парсера (если этот параметр не передан, BeautifulSoup будет использовать его по умолчаниюpythonвстроенный парсерhtml.parser). BeautifulSoup поддерживает множество парсеров, в том числеlxml,html5lib,html.parser.

Пользователь должен установить сторонний синтаксический анализатор, который используется в этой статье.lxmlParser, команда установки выглядит следующим образом (она также должна сначала установить библиотеку языка C).

pip install lxml

Ниже приведен пример, демонстрирующий базовый способ использования BeautifulSoup.Если вы хотите узнать больше, вы можете обратиться кДокументация BeautifulSoup.

from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""

soup = BeautifulSoup(html,'lxml')
# 格式化输出soup中的内容
print(soup.prettify())

# 可以通过.操作符来访问标签对象
title = soup.title
print(title)
p = soup.p
print(p)

# 获得title标签中的文本内容,这2个方法得到的结果是一样的
print(title.text)
print(title.get_text())


# 获得head标签的所有子节点,contents返回的是一个列表,children返回的是一个迭代器
head = soup.head
print(head.contents)
print(head.children)

# 获得所有a标签,并输出每个a标签href属性中的内容
a_tags = soup.find_all('a')
for a_tag in a_tags:
    print(a_tag['href'])
# find函数与find_all一样,只不过返回的是找到的第一个标签    
print(soup.find('a')['href'])

# 根据属性查找,这2个方法得到的结果是一样的
print(soup.find('p',class_='title'))
print(soup.find('p',attrs={'class': 'title'}))

Scrapy


ScrapyЭто мощная платформа сканера, в которой реализована высокопроизводительная структура сканера и предоставляется программистам множество настроек для настройки. использоватьScrapyПросто напишите логику нашего сканера на его правилах.

Сначала нужно установитьScrapy,Выполнение заказаpip install scrapy. а затем выполнить командуscrapy startproject 你的项目名генерироватьScrapyбазовая папка проекта. В результате структура проекта выглядит следующим образом.

你的项目名/
    scrapy.cfg
    你的项目名/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            ...
  • scrapy.cfg: файл конфигурации для проекта.

  • items.py: модуль элемента, пользователь должен определить класс сущности инкапсуляции данных в этом модуле.

  • pipelines.py: модуль конвейера, пользователь должен определить логику обработки данных в этом модуле (например, сохранение в базе данных и т. д.).

  • settings.py: Этот модуль определяет различные переменные конфигурации по всему проекту.

  • spiders/: определите собственный модуль сканера пользователя в этом пакете.

запускатьScrapyКраулер тоже очень простой, достаточно выполнить командуscrapy crawl 你的爬虫名. Представьте нижеScrapyДемонстрации ключевых модулей вScrapyДля получения дополнительной информации см.Официальная документация Scrapy.

items


itemsМодуль в основном предназначен для инкапсуляции просканированных неструктурированных данных в структурированный объект, пользовательскийitemКласс должен наследовать отscrapy.Item, и каждому свойству должно быть присвоено значениеscrapy.Field().

import scrapy

class Product(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    stock = scrapy.Field()

действоватьitemобъект подобен манипулированиюdictтак же просто, как объект.

product = Product()
# 对属性赋值
product['name'] = 'Sylvanas'
product['price'] = 998
# 获得属性
print(product['name'])
print(product['price'])

pipelines


когдаItemОн прибудет после того, как будет упакован сканеромPipelineкласс, вы можете определить свой собственныйPipelineкласс, чтобы решить, кто будетItemстратегия обработки.

каждыйPipelineМогут быть реализованы следующие функции.

  • process_item(item, spider): каждыйPipelineвызовет эту функцию для обработкиItem, функция должна возвращатьItem, если при обработке возникнет ошибка, то ее можно будет выкинутьDropItemаномальный.

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

  • close_spider(spider):когдаspiderЭта функция будет вызываться при закрытии, вы можете использовать эту функцию дляIOресурсы закрыты.

  • from_crawler(cls, crawler): Эта функция используется для полученияsettings.pyсвойства в модуле. Обратите внимание, что эта функция является методом класса.

from scrapy.exceptions import DropItem

class PricePipeline(object):

    vat_factor = 1.15

    def __init__(self, HELLO):
        self.HELLO = HELLO

    def process_item(self, item, spider):
        if item['price']:
            if item['price_excludes_vat']:
                item['price'] = item['price'] * self.vat_factor
            return item
        else:
            raise DropItem("Missing price in %s" % item)

    @classmethod
    def from_crawler(cls, crawler):
        settings = crawler.settings # 从crawler中获得settings
        return cls(settings['HELLO']) # 返回settings中的属性,将由__init__函数接收

когда вы определяете свойPipelineПосле этого также необходимоsettings.pyтебеPipelineСделайте настройки.

ITEM_PIPELINES = {
    # 后面跟的数字是优先级别
    'pipeline类的全路径': 300,
}

spiders


существуетspidersмодуль, пользователь может настроитьSpiderclass, чтобы сформулировать собственную логику сканера и стратегию инкапсуляции данных.каждыйSpiderдолжен наследовать отclass scrapy.spider.Spider,ЭтоScrapyСамый простой базовый класс сканера в , он не имеет специальных функций,ScrapyТакже предоставляет другие функции с различнымиSpiderКлассы доступны пользователям на выбор, поэтому я не буду здесь вдаваться в подробности, вы можете обратиться к официальной документации.

Пользователи могут настраивать конфигурацию с помощью следующих свойств.Spider:

  • name: ЭтоSpiderНазвание,ScrapyНеобходимо найти по этому свойствуSpiderи запустите краулер, он уникален и обязателен.

  • allowed_domains: это свойство указываетSpiderДоменные имена, которые разрешены для обхода.

  • start_urls:SpiderСписок веб-страниц, которые будут сканироваться при запуске.

  • start_requests(): ФункцияSpiderФункция запускается при начале сканирования. Она будет вызываться только один раз. Некоторые веб-сайты должны требовать от пользователей входа в систему. Вы можете использовать эту функцию для имитации входа в систему.

  • make_requests_from_url(url): Функция получаетurlи вернутьсяRequestобъект. Если функция не переопределена, по умолчанию используетсяparse(response)функционировать как функция обратного вызова и включатьdont_filterпараметр (этот параметр используется для фильтрации дубликатовurlиз).

  • parse(response): Когда запрос не устанавливает функцию обратного вызова, она будет вызываться по умолчанию.parse(response).

  • log(message[, level, component]): для регистрации.

  • closed(reason): когдаSpiderВызывается при закрытии.

import scrapy


class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html',
        'http://www.example.com/3.html',
    ]

    def parse(self, response):
        self.log('A response from %s just arrived!' % response.url)

Другие зависимые библиотеки


Requests


Requestsтакже третье лицоpythonбиблиотека, это лучше, чемpythonВстроенныйurllibПроще и удобнее в использовании. Просто установите (pip install requests), а после импорта пакета вы можете легко инициировать запрос на веб-сайт.

import requests

# 支持http的各种类型请求
r = requests.post("http://httpbin.org/post")
r = requests.put("http://httpbin.org/put")
r = requests.delete("http://httpbin.org/delete")
r = requests.head("http://httpbin.org/get")
r = requests.options("http://httpbin.org/get")

# 获得响应内容
r.text # 返回文本
r.content # 返回字节
r.raw # 返回原始内容
r.json() # 返回json

Дополнительные параметры и содержание см.ЗапросыДокументация.

BloomFilter


BloomFilterэто структура данных, используемая для фильтрации повторяющихся данных, мы можем использовать ее дляurlфильтровать. используется в этой статьеBloomFilterОтpython-bloomfilter, другие пользователи операционной системы, пожалуйста, используйтеpip install pybloomКомандная установка, пользователи Windows, пожалуйста, используйтеpip install pybloom-live(Оригинал не подходит для Windows).

анализировать


После введения необходимых библиотек зависимостей мы, наконец, можем приступить к реализации нашего собственного сканера изображений. Наша цель - поднятьсяhttps://www.deviantart.com/Прежде чем писать программу-краулер, вам необходимо проанализировать страницы изображений на веб-сайте.HTMLструктура, так что исходный адрес изображения может быть найден целенаправленным образом.

Дабы удостовериться в качестве просканированных картинок, я решил начать обход с популярных страниц, ссылкаhttps://www.deviantart.com/whats-hot/.

После открытия инструментов разработчика браузера вы можете обнаружить, что каждое изображение создаетсяaэтикетки, каждаяaпомеченclassзаtorpedo-thumb-link, и этоaпомеченhrefЭто страница подробностей этой картинки (если мы начнем сканировать картинки отсюда, то мы будем сканировать только миниатюры).

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

После получения исходного адреса изображения моя стратегия состоит в том, чтобы позволить сканеру продолжить сканирование других изображений, рекомендованных на странице.С помощью инструментов разработчика можно обнаружить, что эти изображения инкапсулированы вclassзаtt-crop thumbизdivтег, и первыйaВложенный тег — это просто ссылка на страницу сведений об изображении.

Начальная конфигурация


в сетиHTMLПосле анализа можно приступать к написанию программы, сначала использоватьScrapyКоманда для инициализации проекта. позжеsettings.pyВыполните следующую настройку.

# 这个是网络爬虫协议,爬虫访问网站时都会检查是否有robots.txt文件,
# 然后根据文件中的内容选择性地进行爬取,我们这里设置为False即不检查robots.txt
ROBOTSTXT_OBEY = False

# 图片下载的根目录路径
IMAGES_STORE = '.'

# 图片最大下载数量,当下载的图片达到这个数字时,将会手动关闭爬虫
MAXIMUM_IMAGE_NUMBER = 10000

затем определите нашItem.

import scrapy


class DeviantArtSpiderItem(scrapy.Item):
    author = scrapy.Field() # 作者名
    image_name = scrapy.Field() # 图片名
    image_id = scrapy.Field() # 图片id
    image_src = scrapy.Field() # 图片的源地址

создай свой собственныйspiderмодуль сSpiderсвоего рода.

import requests
from bs4 import BeautifulSoup
# this import package is right,if PyCharm give out warning please ignore
from deviant_art_spider.items import DeviantArtSpiderItem
from pybloom_live import BloomFilter
from scrapy.contrib.linkextractors.lxmlhtml import LxmlLinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.http import Request

class DeviantArtImageSpider(CrawlSpider):
    name = 'deviant_art_image_spider'

    # 我不想让scrapy帮助过滤所以设置为空
    allowed_domains = ''

    start_urls = ['https://www.deviantart.com/whats-hot/']

    rules = (
        Rule(LxmlLinkExtractor(
            allow={'https://www.deviantart.com/whats-hot/[\?\w+=\d+]*', }),
            callback='parse_page', # 设置回调函数
            follow=True # 允许爬虫不断地跟随链接进行爬取
        ),
    )

    headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36"
                      " (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
        "Referer": "https://www.deviantart.com/"
    }

    # 初始化BloomFilter
    filter = BloomFilter(capacity=15000)

DeviantArtImageSpiderунаследовано отCrawlSpider, классScrapyчаще всего используетсяSpiderкласс, который проходитRuleкласс для определения правил обхода ссылок, в приведенном выше коде используются регулярные выраженияhttps://www.deviantart.com/whats-hot/[\?\w+=\d+]*, это регулярное выражение будет посещать первые страницы для каждой страницы.

Парсинг популярных страниц


Когда краулер запустится, он сначала посетит популярную страницу, а после ответа на запрос будет вызвана функция обратного вызова, в которой нам необходимо получить данные, полученные при анализе выше.<a class = 'torpedo-thumb-link'>тег, а затем извлеките ссылку на страницу сведений о каждом изображении.

    def parse_page(self, response):
        soup = self._init_soup(response, '[PREPARING PARSE PAGE]')
        if soup is None:
            return None
        # 找到所有class为torpedo-thumb-link的a标签    
        all_a_tag = soup.find_all('a', class_='torpedo-thumb-link')
        if all_a_tag is not None and len(all_a_tag) > 0:
            for a_tag in all_a_tag:
                # 提取图片详情页,然后对详情页链接发起请求,并设置回调函数
                detail_link = a_tag['href']
                request = Request(
                    url=detail_link,
                    headers=self.headers,
                    callback=self.parse_detail_page
                )
                # 通过request与response对象来传递Item
                request.meta['item'] = DeviantArtSpiderItem()
                yield request
        else:
            self.logger.debug('[PARSE FAILED] get <a> tag failed')
            return None

    # 初始化BeautifulSoup对象
    def _init_soup(self, response, log):
        url = response.url
        self.headers['Referer'] = url
        self.logger.debug(log + ' ' + url)
        body = requests.get(url, headers=self.headers, timeout=2).content
        soup = BeautifulSoup(body, 'lxml')
        if soup is None:
            self.logger.debug('[PARSE FAILED] read %s body failed' % url)
            return None
        return soup

Разобрать страницу сведений


parse_page()Функция будет постоянно отправлять запросы на ссылку страницы сведений, а функция обратного вызова для анализа страницы сведений должна обрабатывать данные и инкапсулировать их вItem, вам также необходимо извлечь ссылку на дополнительные изображения на странице сведений и отправить запрос.

    def parse_detail_page(self, response):
        if response.url in self.filter:
            self.logger.debug('[REPETITION] already parse url %s ' % response.url)
            return None
        soup = self._init_soup(response, '[PREPARING DETAIL PAGE]')
        if soup is None:
            return None
        # 包装Item并返回    
        yield self.packing_item(response.meta['item'], soup)
        self.filter.add(response.url)
        # 继续抓取当前页中的其他图片
        all_div_tag = soup.find_all('div', class_='tt-crop thumb')
        if all_div_tag is not None and len(all_div_tag) > 0:
            for div_tag in all_div_tag:
                detail_link = div_tag.find('a')['href']
                request = Request(
                    url=detail_link,
                    headers=self.headers,
                    callback=self.parse_detail_page
                )
                request.meta['item'] = DeviantArtSpiderItem()
                yield request
        else:
            self.logger.debug('[PARSE FAILED] get <div> tag failed')
            return None

    # 封装数据到Item
    def packing_item(self, item, soup):
        self.logger.debug('[PREPARING PACKING ITEM]..........')
        img = soup.find('img', class_='dev-content-full')
        img_alt = img['alt'] # alt属性中保存了图片名与作者名
        item['image_name'] = img_alt[:img_alt.find('by') - 1]
        item['author'] = img_alt[img_alt.find('by') + 2:]
        item['image_id'] = img['data-embed-id'] # data-embed-id属性保存了图片id
        item['image_src'] = img['src']
        self.logger.debug('[PACKING ITEM FINISHED] %s ' % item)
        return item

Обращение с предметами


заItemОбработка заключается в том, чтобы просто назвать и загрузить изображение на локальный. Я не использую многопроцессорность или многопоточность, а также не используюScrapyавтономныйImagePipeline(Степень свободы невелика), и заинтересованная детская обувь может реализовать ее самостоятельно.

import requests
import threading
import os
from scrapy.exceptions import DropItem, CloseSpider


class DeviantArtSpiderPipeline(object):
    def __init__(self, IMAGE_STORE, MAXIMUM_IMAGE_NUMBER):
        if IMAGE_STORE is None or MAXIMUM_IMAGE_NUMBER is None:
            raise CloseSpider('Pipeline load settings failed')
        self.IMAGE_STORE = IMAGE_STORE
        self.MAXIMUM_IMAGE_NUMBER = MAXIMUM_IMAGE_NUMBER
        # 记录当前下载的图片数量
        self.image_max_counter = 0
        # 根据图片数量创建文件夹,每1000张在一个文件夹中
        self.dir_counter = 0

    def process_item(self, item, spider):
        if item is None:
            raise DropItem('Item is null')
        dir_path = self.make_dir()
        # 拼接图片名称
        image_final_name = item['image_name'] + '-' + item['image_id'] + '-by@' + item['author'] + '.jpg'
        dest_path = os.path.join(dir_path, image_final_name)
        self.download_image(item['image_src'], dest_path)
        self.image_max_counter += 1
        if self.image_max_counter >= self.MAXIMUM_IMAGE_NUMBER:
            raise CloseSpider('Current downloaded image already equal maximum number')
        return item

    def make_dir(self):
        print('[IMAGE_CURRENT NUMBER] %d ' % self.image_max_counter)
        if self.image_max_counter % 1000 == 0:
            self.dir_counter += 1
        path = os.path.abspath(self.IMAGE_STORE)
        path = os.path.join(path, 'crawl_images')
        path = os.path.join(path, 'dir-' + str(self.dir_counter))
        if not os.path.exists(path):
            os.makedirs(path)
            print('[CREATED DIR] %s ' % path)
        return path

    def download_image(self, src, dest):
        print('[Thread %s] preparing download image.....' % threading.current_thread().name)
        response = requests.get(src, timeout=2)
        if response.status_code == 200:
            with open(dest, 'wb') as f:
                f.write(response.content)
                print('[DOWNLOAD FINISHED] from %s to %s ' % (src, dest))
        else:
            raise DropItem('[Thread %s] request image src failed status code = %s'
                           % (threading.current_thread().name, response.status_code))

    @classmethod
    def from_crawler(cls, crawler):
        settings = crawler.settings
        return cls(settings['IMAGES_STORE'], settings['MAXIMUM_IMAGE_NUMBER'])

существуетsettings.pyзарегистрироватьPipeline

ITEM_PIPELINES = {
    'deviant_art_spider.pipelines.DeviantArtSpiderPipeline': 300,
}

Пул IP-прокси


Некоторые веб-сайты имеют механизмы защиты от сканирования, чтобы решить эту проблему, каждый запрос использует разныеIPПрокси, есть много сайтов, которые предлагаютIPПрокси-сервис, нам нужно написать краулер изоблачный проксипросканируй это бесплатноIPпрокси (бесплатноIPОчень нестабильно, и после того, как я использовал прокси, различные запросы терпели неудачу Orz...).

import os

import requests
from bs4 import BeautifulSoup


class ProxiesSpider(object):
    def __init__(self, max_page_number=10):
        self.seed = 'http://www.ip3366.net/free/'
        self.max_page_number = max_page_number # 最大页数
        self.crawled_proxies = [] # 爬到的ip,每个元素都是一个dict
        self.verified_proxies = [] # 校验过的ip
        self.headers = {
            'Accept': '*/*',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)'
                          ' Chrome/45.0.2454.101 Safari/537.36',
            'Accept-Language': 'zh-CN,zh;q=0.8'
        }
        self.tocrawl_url = []

    def crawl(self):
        self.tocrawl_url.append(self.seed)
        page_counter = 1
        while self.tocrawl_url:
            if page_counter > self.max_page_number:
                break
            url = self.tocrawl_url.pop()
            body = requests.get(url=url, headers=self.headers, params={'page': page_counter}).content
            soup = BeautifulSoup(body, 'lxml')
            if soup is None:
                print('PARSE PAGE FAILED.......')
                continue
            self.parse_page(soup)
            print('Parse page %s done' % (url + '?page=' + str(page_counter)))
            page_counter += 1
            self.tocrawl_url.append(url)
        self.verify_proxies()
        self.download()

    # 解析页面并封装
    def parse_page(self, soup):
        table = soup.find('table', class_='table table-bordered table-striped')
        tr_list = table.tbody.find_all('tr')
        for tr in tr_list:
            ip = tr.contents[1].text
            port = tr.contents[3].text
            protocol = tr.contents[7].text.lower()
            url = protocol + '://' + ip + ':' + port
            self.crawled_proxies.append({url: protocol})
            print('Add url %s to crawled_proxies' % url)

    # 对ip进行校验
    def verify_proxies(self):
        print('Start verify proxies.......')
        while self.crawled_proxies:
            self.verify_proxy(self.crawled_proxies.pop())
        print('Verify proxies done.....')

    def verify_proxy(self, proxy):
        proxies = {}
        for key in proxy:
            proxies[str(proxy[key])] = key # requests的proxies的格式必须为 协议 : 地址
        try:
            if requests.get('https://www.deviantart.com/', proxies=proxies, timeout=2).status_code == 200:
                print('verify proxy success %s ' % proxies)
                self.verified_proxies.append(proxy)
        except:
            print('verify proxy fail %s ' % proxies)

    # 保存到文件中
    def download(self):
        current_path = os.getcwd()
        parent_path = os.path.dirname(current_path)
        with open(parent_path + '\proxies.txt', 'w') as f:
            for proxy in self.verified_proxies:
                for key in proxy.keys():
                    f.write(key + '\n')


if __name__ == '__main__':
    spider = ProxiesSpider()
    spider.crawl()

ПолучилIPПосле пула проксиScrapyизmiddlewares.pyМодули определяют классы промежуточного программного обеспечения прокси.

import time
from scrapy import signals
import os
import random


class ProxyMiddleware(object):
    # 每次请求前从IP代理池中选择一个IP代理并进行设置
    def process_request(self, request, spider):
        proxy = self.get_proxy(self.make_path())
        print('Acquire proxy %s ' % proxy)
        request.meta['proxy'] = proxy

    # 请求失败,重新设置IP代理
    def process_response(self, request, response, spider):
        if response.status != 200:
            proxy = self.get_proxy(self.make_path())
            print('Response status code is not 200,try reset request proxy %s ' % proxy)
            request.meta['proxy'] = proxy
            return request
        return response

    def make_path(self):
        current = os.path.abspath('.')
        parent = os.path.dirname(current)
        return os.path.dirname(parent) + '\proxies.txt'

    # 从IP代理文件中随机获得一个IP代理地址
    def get_proxy(self, path):
        if not os.path.isfile(path):
            print('[LOADING PROXY] loading proxies failed proxies file is not exist')
        while True:
            with open(path, 'r') as f:
                proxies = f.readlines()
            if proxies:
                break
            else:
                time.sleep(1)
        return random.choice(proxies).strip()

Наконец вsettings.pyзарегистрироваться.

DOWNLOADER_MIDDLEWARES = {
    # 这个中间件是由scrapy提供的,并且它是必需的
    'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 543, 
    # 我们自定义的代理中间件
    'deviant_art_spider.middlewares.ProxyMiddleware': 540 
}

End


Наш сканер изображений завершен, выполните командуscrapy crawl deviant_art_image_spider, а затем наслаждайтесь коллекционированием картинок!

Если вы хотите получить полный исходный код и поисковый робот P station в этой статье, пожалуйста, нажмите на меня и, кстати, попросите поставить звездочку...

Недавно мне захотелось написать о рептилиях по прихоти, поэтому мне потребовалось время, чтобы пройтись по ней.pythonГрамматика запущена на скорую руку, а код какой-то некрасивый и недостаточно питоничный, прошу жалобу у официалов.