多线程分布式爬虫

一. 爬虫的种类

1. 普通爬虫

for url in urls:
    url -> 发送请求 ->  获得response ->  解析response -> 保存数据

2. 多线程爬虫

urls 保存在本地内存中
work(url -> 发送请求 ->  获得response ->  解析response -> 保存数据)
启用多个work

3. 多线程分布式爬虫

urls 保存在redis内存数据库中
多台电脑 从redis内存数据库取url,
每台电脑执行的操作是:work(url -> 发送请求 ->  获得response ->  解析response -> 保存数据)

二. 多线程分布式爬虫教程

以爬取https://weixin.sogou.com/weixin?query=%E6%90%9E%E7%AC%91&type=2&page=1&ie=utf8的每个文章为例,最终将爬取出的数据存入mysql数据库中

1. master

master的作用是将循环解析网页的每一个页面,取出每个文章的url,放入Redis数据库中,供server调用

1. 连接到mysql,建立数据模型

1. ORM
  • Object Relational Mapping,简称ORM

  • 将数据模型对象化

  • 简化了数据库查询过程

  • 开发人员操作数据时,只需关系对象之间的关系、对象的属性方法即可,而不必关心数据库的底层结构。

    class Article(Base):
        __tablename__ = 'articles'
        id = Column(Integer,  autoincrement=True,primary_key=True)
        headline = Column(String(200),nullable=False)
        link = Column(String(200),nullable=False)
        abstract = Column(String(500),nullable=True)
        content = Column(String(5000),nullable=True)
    
        def __str__(self):
            return self.headline
    

2. SQLAlchemy是一款ORM框架;
  • 安装 pip install sqlalchemy
  • Alchemy [`ælkəmi] 魔法
  • 它将对象转换成SQL,将SQL的操作对象化。

常见问题及解决办法:

报错:no module named mysql
解决办法:pip install mysql-connector-python

3. 附上models.py文件的全部代码
from sqlalchemy import Column, String, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()


class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer, autoincrement=True, primary_key=True)
    headline = Column(String(200), nullable=False)
    link = Column(String(200), nullable=False)
    abstract = Column(String(500), nullable=True)
    content = Column(String(5000), nullable=True)

    def __str__(self):
        return self.headline


engine = create_engine('mysql+mysqlconnector://hua:hua@localhost:3306/spider')


def init_db():
    Base.metadata.create_all(engine)


def drop_db():
    Base.metadata.drop_all(engine)


def get_session():
    DBSession = sessionmaker(bind=engine)
    session = DBSession()
    return session


if __name__ == '__main__':
    session = get_session()
    article = Article(headline='bbbb', link='aaaa', abstract='cccc', content='dddd')
    # 查询
    articles = session.query(Article).all()
    for a in articles:
        print(a)
    # 增加
    session.add(article)
    # 删除
    # session.delete(article)
    session.commit()
    session.close()

2. master中的spider

1. 解析网页,取出每个文章的url

使用xpath解析请求到的网页

def parse_page(page):
    """
    :param page:页码
    :return:网页中所有文章的url
    """
    url = 'https://weixin.sogou.com/weixin?query=%E6%90%9E%E7%AC%91&type=2&page={page}&ie=utf8'.format(page=page)
    resp = requests.get(url, verify=False)
    tree = etree.HTML(resp.content)
    urls = tree.xpath('//li/div[@class="txt-box"]/h3/a/@href')
    return urls

这里有个坑:在 requests.get(url, verify=False) 中,verify这个参数

如果设置True,即为启用证书验证,直接运行我的代码请求不到数据。

如果设置为False,如果在未启用证书验证的情况下向HTTPS URL发出请求,则会报错:

InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
  InsecureRequestWarning

官网给出了解决办法:

为了启用验证,您需要一组根证书。最简单,最可靠的方法是使用提供Mozilla根证书包的certifi包:

pip install certifi

您还可以使用secure 额外的安装与urllib3一起安装certifi :

pip install urllib3[secure]

获得证书后,您可以创建PoolManager 在发出请求时验证证书的证书:

>>> import certifi
>>> import urllib3
>>> http = urllib3.PoolManager(
...     cert_reqs='CERT_REQUIRED',
...     ca_certs=certifi.where())

PoolManager会自动处理证书验证,并会提高SSLError,如果验证失败:

>>> http.request('GET', 'https://google.com')
(No exception)
>>> http.request('GET', 'https://expired.badssl.com')
urllib3.exceptions.SSLError ...

注意:

如果需要,您可以使用OS提供的证书。只需指定证书包的完整路径作为ca_certs参数而不是 certifi.where()。例如,大多数Linux系统都存储证书/etc/ssl/certs/ca-certificates.crt。其他操作系统可能很难

2. 将解析出的url添加到Redis数据库中

建立redis连接

遍历出需要的网址,放到解析函数中,将返回的数据存入redis数据中,等待server的调用

from redis import Redis

REDIS_SPIDER_URLS_KEY = 'weixin_urls'
rds = Redis('127.0.0.1', 6379)

def init_queue():
    """循环遍历10个网址,把解析出来的每个链接添加到Redis数据库中"""
    page = 1
    while True:
        page += 1
        urls = parse_page(page)
        for url in urls:
            rds.lpush(REDIS_SPIDER_URLS_KEY, url)
        if page == 10:
            break
3. spider的全部代码
from redis import Redis
import requests
from lxml import etree
import urllib3

urllib3.disable_warnings()

REDIS_SPIDER_URLS_KEY = 'weixin_urls'
rds = Redis('127.0.0.1', 6379)


def parse_page(page):
    """
    :param page:页码
    :return:网页中所有文章的url
    """
    url = 'https://weixin.sogou.com/weixin?query=%E6%90%9E%E7%AC%91&type=2&page={page}&ie=utf8'.format(page=page)
    resp = requests.get(url, verify=False)
    tree = etree.HTML(resp.content)
    urls = tree.xpath('//li/div[@class="txt-box"]/h3/a/@href')
    return urls


def init_queue():
    """循环遍历10个网址,把解析出来的每个链接添加到Redis数据库中"""
    page = 1
    while True:
        page += 1
        urls = parse_page(page)
        for url in urls:
            rds.lpush(REDIS_SPIDER_URLS_KEY, url)
        if page == 10:
            break


if __name__ == '__main__':
    init_queue()

2. server

  1. 连接Redis
  2. 从Redis中取值
  3. 解析网页,将数据存储到mysql数据库中(存储方法见master的models.py文件,我偷懒就没存储)

附上spider.py的源码

from redis import Redis
import requests
from lxml import etree

REDIS_SPIDER_URLS_KEY = 'weixin_urls'
rds = Redis('127.0.0.1', 6379)


def parse_detail_page(url):
    """
    从详情页中提取需要的数据,如果提取成功,返回提取的数据,否则返回None
    :param url:详情页的url
    :return:字典格式的item
    """
    # 获取内容
    resp = requests.get(url, verify=False)
    tree = etree.HTML(resp.content)
    # 直接取标签文本用text()  return  []
    headline = tree.xpath('//h2/text()')
    # 跨标签取文本用string    return str
    content = tree.xpath('string(//div[contains(@class,"rich_media_content")])')
    # 保证内容不为空
    item = {}
    if headline and content:
        item['headline'] = headline[0].strip()
        item['content'] = content.strip()
        print(item)
    return item or None


def work():
    while True:
        url = rds.lpop(REDIS_SPIDER_URLS_KEY)
        print(url)
        if url:
            parse_detail_page(url)
        break


if __name__ == '__main__':
    work()


至此,一个简单的多线程分布式爬虫就完成了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值