for url in urls:
url -> 发送请求 -> 获得response -> 解析response -> 保存数据
urls 保存在本地内存中
work(url -> 发送请求 -> 获得response -> 解析response -> 保存数据)
启用多个work
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数据库中
master的作用是将循环解析网页的每一个页面,取出每个文章的url,放入Redis数据库中,供server调用
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
常见问题及解决办法:
报错:no module named mysql
解决办法:pip install mysql-connector-python
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()
使用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
。其他操作系统可能很难。
建立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
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()
附上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()
至此,一个简单的多线程分布式爬虫就完成了