python 爬虫开发所需基础知识 - urllib库的基本使用

urllib库的基本使用

官方文档地址: https://docs.python.org/3/library/urllib.html

什么是urllib

是python内置的HTTP请求库 包括以下模块:

  • urllib.request: 请求模块
  • urllib.error: 异常处理模块
  • urllib.parse: url解析模块
  • urllib.robotparser:robots.txt解析模块

urllib.request.urlopen

用法: urllib.request.urlopen(url, data=None, [timeoout]*, cafile=None, capth=None, cadefault=False, context=None)

url参数的使用

import urllib.request

response = urllib.request.urlopen('http://www.baidu.com')
print(response.read().decode('utf-8'))

data参数的使用 data是向url发送请求时需要附带的数据,如果提供了data,则是发送post请求,如果为提供data,则发送的是get请求

import urllib.parse, urllib.request

data = bytes(urllib.parse.urlencode({'word':'hello'}), encoding='utf-8')
print(data)
response = urllib.request.urlopen('http://httpbin.org/post',data=data)
print(response.read())

timeout参数的使用 这个参数限定发送请求后等待结果的时间。

import urllib.request

response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
print(response.read())

当返回结果的时间超过了timeout,会抛出URLError,因此可以捕捉这个错误:

import socket, urllib.request, urllib.error

try:
    response = urllib.request.urlopen('http://httpbin.org/get',timeout=0.1)
except urllib.error.URLError as e:
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT')

响应类型,状态码,响应头

import urllib.request

response = urllib.request.urlopen('https://www.python.org')
print(type(response))

可以通过response.status, response.getheaders(), response.getheader('server')获取状态码,头部信息

urllib.request

设置headers

from urllib import request, parse

url = 'http://httpbin.org/post'
headers = {
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
    'Host': 'httpbin.org'
}

dict = {'name':'zhaofan'}

data = bytes(parse.urlencode(dict), encoding='utf-8')
req = request.Request(url=url,data=data,headers=headers,method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

高级用法--各种handler

代理,ProxyHandler 通过urllib.request.ProxyHandler()可以设置代理。

import urllib.request

proxy_handler = urllib.request.ProxyHandler({
    'http': 'http://127.0.0.1:9743',
    'https': 'https://127.0.0.1:9743'
    })
opener = urllib.request.build_opener(proxy_handler)
response = opener.open('http://httpbin.org/get')
print(response.read())

cookie, HTTPCookieProcessor cookie中保存常见的登录信息, python用http.cookiejar获取cookie及存储cookie:

import http.cookiejar, urllib.request

cookie = http.cookiejar.CookieJar()

handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')

for item in cookie:
    print(item.name+'='+item.value)

cookie可以写入到文件中保存,有两种方式: http.cookiejar.MozillaCookieJar, http.cookiejar.LWPCookieJar:

使用http.cookiejar.MozillaCookieJar()方式:

import http.cookiejar, urllib.request

filename = 'cookie.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)

使用http.cookiejar.LWPCookieJar()方式:

import http.cookiejar, urllib.request
filename = 'cookie.txt'

cookie = http.cookiejar.LWPCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)

如果想获取文件中的cookie的话,可以通过load的方式:

import http.cookiejar, urllib.request

cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))

URL解析

urllib.parse.urlparse() 用法: urllib.parse.urlparse(url, scheme='', allow_fragments=True)

from urllib.parse import urlparse

result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(result)

Requests库的基本使用

Requests是基于urllib编写的,它比urllib更加方便。

requests功能详解

import requests

response = requests.get('https://www.baidu.com')

requests提供了5中请求方式:

requests.post("http://httpbin.org/post")
requests.put("http://httpbin.org/put")
requests.delete("http://httpbin.org/delete")
requests.head("http://httpbin.org/get")
requests.options("http://httpbin.org/get")

GET请求

import requests
data = {
    "name":"zhaofan",
    "age":22
}
response = requests.get("http://httpbin.org/get",params=data)

解析json

import requests, json

response = requests.get('http://httpbin.org/get')
print(response.json())
print(json.loads(response.text))

上面两种读取json的方式结果是一样的。

获取二进制数据 可以通过response.content获取二进制数据

添加headers

import requests

headers = {
    "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}
response = requests.get('https://www.zhihu.com', headers=headers)

POST请求

import requests

data = {
    "name":"zhaofan",
    "age":23
}
response = requests.post("http://httpbin.org/post",data=data)
print(response.text)

requests高级用法

文件上传

import requests
files= {"files":open("git.jpeg","rb")}
response = requests.post("http://httpbin.org/post",files=files)
print(response.text)

获取cookie

import requests

response = requests.get("http://www.baidu.com")
print(response.cookies)

for key,value in response.cookies.items():
    print(key+"="+value)

会话维持

import requests
s = requests.Session()
s.get("http://httpbin.org/cookies/set/number/123456")
response = s.get("http://httpbin.org/cookies")
print(response.text)

证书验证

import requests
from requests.packages import urllib3
urllib3.disable_warnings()
response = requests.get("https://www.12306.cn",verify=False)
print(response.status_code)

代理设置

import requests

proxies= {
    "http":"http://127.0.0.1:9999",
    "https":"http://127.0.0.1:8888"
}
response  = requests.get("https://www.baidu.com",proxies=proxies)
print(response.text)

超时设置 通过timeout参数可以设置超时的时间

认证设置 可以通过requests.auth模块实现

import requests

from requests.auth import HTTPBasicAuth

response = requests.get("http://120.27.34.24:9001/",auth=HTTPBasicAuth("user","123"))
print(response.status_code)

异常处理

import requests

from requests.exceptions import ReadTimeout,ConnectionError,RequestException


try:
    response = requests.get("http://httpbin.org/get",timout=0.1)
    print(response.status_code)
except ReadTimeout:
    print("timeout")
except ConnectionError:
    print("connection Error")
except RequestException:
    print("error")

正则表达式

匹配模式

\w 匹配字母数字及下划线 \W 匹配f非字母数字下划线 \s 匹配任意空白字符,等价于[\t\n\r\f] \S 匹配任意非空字符 \d 匹配任意数字 \D 匹配任意非数字 \A 匹配字符串开始 \Z 匹配字符串结束,如果存在换行,只匹配换行前的结束字符串 \z 匹配字符串结束 \G 匹配最后匹配完成的位置 \n 匹配一个换行符 \t 匹配一个制表符 ^ 匹配字符串的开头 $ 匹配字符串的末尾 . 匹配任意字符,除了换行符,re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符 [....] 用来表示一组字符,单独列出:[amk]匹配a,m或k [^...] 不在[]中的字符:[^abc]匹配除了a,b,c之外的字符

  •   匹配0个或多个的表达式
    
  •   匹配1个或者多个的表达式
    

? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 {n} 精确匹配n前面的表示 {m,m} 匹配n到m次由前面的正则表达式定义片段,贪婪模式 a|b 匹配a或者b () 匹配括号内的表达式,也表示一个组

使用re.match()

语法格式: re.match(pattern, string, flags=0)

常规匹配

import re

content= "hello 123 4567 World_This is a regex Demo"
result = re.match('^hello\s\d\d\d\s\d{4}\s\w{10}.*Demo$',content)
print(result)

# 获取匹配的结果
print(result.group())

# 获取匹配字符串的长度范围
print(result.span())

使用re.search

扫描整个字符串返回第一个成功匹配的结果

import re

content = "extra things hello 123455 world_this is a Re Extra things"

result = re.search("hello.*?(\d+).*?Re",content)
print(result)
print(result.group())
print(result.group(1))

使用re.findall

搜索字符串,以列表的形式返回全部能匹配的子串

import re

html = '''<div id="songs-list">
    <h2 class="title">经典老歌</h2>
    <p class="introduction">
        经典老歌列表
    </p>
    <ul id="list" class="list-group">
        <li data-view="2">一路上有你</li>
        <li data-view="7">
            <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
        </li>
        <li data-view="4" class="active">
            <a href="/3.mp3" singer="齐秦">往事随风</a>
        </li>
        <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
        <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
        <li data-view="5">
            <a href="/6.mp3" singer="邓丽君">但愿人长久</a>
        </li>
    </ul>
</div>'''

results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S)
print(results)
print(type(results))
for result in results:
    print(result)
    print(result[0], result[1], result[2])

使用re.sub

替换字符串中每一个匹配的子串后返回替换后的字符串 用法: re.sub(正则表达式, 替换成的字符串, 原字符串)

import re

content = "Extra things hello 123455 World_this is a regex Demo extra things"

content = re.sub('\d+','',content)
print(content)

re.compile

将正则表达式编译成正则表达式对象,方便复用该正则表达式

import re
content= """hello 12345 world_this
123 fan
"""

pattern =re.compile("hello.*fan",re.S)

result = re.match(pattern,content)
print(result)
print(result.group())

beautifulsoup库的使用

一个灵活又方便的网页解析库,支持多种解析器,利用它就不用编写正则表达式也能方便的实现网页信息的抓取。

快速使用

from bs4 import BeautifulSoup

html = '''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><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')
print(soup.prettify())
print(soup.title)
print(soup.title.name)
print(soup.title.string)
print(soup.title.parent.name)
print(soup.p)
print(soup.p["class"])
print(soup.a)
print(soup.find_all('a'))
print(soup.find(id='link3'))

for link in soup.find_all('a'):
    print(link.get('href'))

print(soup.get_text())

解析器

常见的解析器:

推荐使用xml作为解释器。

基本使用

标签选择器 通过soup.标签名,可以获得这个标签的内容。

获取名称 通过soup.title.name可以获得该title标签的名称。

获取属性 soup.p.attrs['name'] soup.p['name'] 这两种方式都可以获取p标签的name属性

获取内容 soup.p.string 获取第一个p标签的内容

PyQuery库的使用

仿照jquery的网页解析库 http://pyquery.readthedocs.io/en/latest/

基本使用

html = '''
<div>
    <ul>
         <li class="item-0">first item</li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1 active"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
</div>
'''

from pyquery import PyQuery as pq
doc = pq(html)
print(doc)
print(type(doc))
print(doc('li'))

从URL初始化

from pyquery import PyQuery as pq

doc = pq(url='http://www.baidu.com', encoding='utf-8')
print(doc('head'))

doc2 = pq(filename='index.html')

基本的CSS选择器

参照文档

使用selenium库

selenium是一套完整的web应用程序测试系统,包含了测试的录制,编写及运行和测试的并行处理。它可以模拟真实浏览器,自动化测试工具,支持多种浏览器。

selenium的基本使用

在python爬虫中,主要用的是selenium的WebDriver。

声明浏览器对象

from selenium import webdriver

browser = webdriver.Chrome()
browser = webdriver.Firefox()

访问页面

from selenium import webdriver

browser = webdriver.Chrome()
browser.get('http://www.baidu.com')
print(browser.page_source)
browser.close()

查找元素

from selenium import webdriver

browser = webdriver.Chrome()

browser.get("http://www.taobao.com")
input_first = browser.find_element_by_id("q")
input_second = browser.find_element_by_css_selector("#q")
input_third = browser.find_element_by_xpath('//*[@id="q"]')
print(input_first)
print(input_second)
print(input_third)
browser.close()

这里列举一下常用的查找元素方法:

find_element_by_name find_element_by_id find_element_by_xpath find_element_by_link_text find_element_by_partial_link_text find_element_by_tag_name find_element_by_class_name find_element_by_css_selector

元素交互操作 对于获取的元素调用交互方法

from selenium import webdriver

import time

browser = webdriver.Chrome()
browser.get("http://www.taobao.com")
input_str = browser.find_element_by_id('q')
input_str.send_keys("ipad")
time.sleep(1)
input_str.clear()
input_str.send_keys("MakBook pro")
button = browser.find_element_by_class_name('btn-search')
button.click()

执行JavaScript

from selenium import webdriver
browser = webdriver.Chrome()
browser.get("http://www.zhihu.com/explore")
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')

隐式等待 到了一定的时间发现元素还没有加载,则继续等待我们指定的时间。 如果超过了我们指定的时间还没有加载就会抛出异常。

from selenium import webdriver

browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu.com/explore')
input = browser.find_element_by_class_name('zu-top-add-question')
print(input)

显示等待 指定一个等待条件,并指定一个最长等待时间,会在这个时间内判断是否满足等待条件。如果条件成立就会立即返回,如果不成立,则一直等待到指定的最长等待时间,超过等待时间就会抛出异常

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
wait = WebDriverWait(browser, 10)
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))
print(input, button)

浏览器的前进和后退 back(), forward()

cookie操作 get_cookies(), delete_all_cookies(), add_cookie()

from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'zhaofan'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())

选项卡管理 打开新选项卡: window.open() 不同的选项卡是存在列表里browser.window_handles

import time
from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')
print(browser.window_handles)
browser.switch_to_window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(1)
browser.switch_to_window(browser.window_handles[0])
browser.get('https://python.org')

爬虫的整体思路

Scrapy的初步认识

scrapy使用了twisted作为框架。twisted是事件驱动的,适合异步代码。

创建scrapy工程 scrapy startproject test1 cd test1 scrapy genspider example example.com

items.py代码分析 items.py存放的是要爬取数据的字段信息。

class JoBoleArticleItem(scrapy.Item):
    title = scrapy.Field()
    create_date = scrapy.Field()
    url = scrapy.Field()
    url_object_id = scrapy.Field()
    front_image_url = scrapy.Field()
    front_image_path = scrapy.Field()
    praise_nums = scrapy.Field()
    fav_nums = scrapy.Field()
    comment_nums = scrapy.Field()
    tag = scrapy.Field()
    content = scrapy.Field()

spiders/Article.py代码分析 spiders目录下的Article.py为主要的爬虫代码,包括了对页面的请求及页面的处理:

class ArticleSpider(scrapy.Spider):
    name = "Article"
    allowed_domains = ["blog.jobbole.com"]
    start_urls = ['http://blog.jobbole.com/all-posts/']

    def parse(self, response):
        '''
        1.获取文章列表也中具体文章url,并交给scrapy进行下载后并进行解析
        2.获取下一页的url并交给scrapy进行下载,下载完成后,交给parse
        :param response:
        :return:
        '''
        #解析列表页中所有文章的url,并交给scrapy下载后进行解析
        post_nodes = response.css("#archive .floated-thumb .post-thumb a")
        for post_node in post_nodes:
            #image_url是图片的地址
            image_url = post_node.css("img::attr(src)").extract_first("")
            post_url = post_node.css("::attr(href)").extract_first("")
            #这里通过meta参数将图片的url传递进来,这里用parse.urljoin的好处是如果有域名我前面的response.url不生效
            # 如果没有就会把response.url和post_url做拼接
            yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":parse.urljoin(response.url,image_url)},callback=self.parse_detail)

        #提取下一页并交给scrapy下载
        next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
        if next_url:
            yield Request(url=next_url,callback=self.parse)

    def parse_detail(self,response):
        '''
        获取文章的详细内容
        :param response:
        :return:
        '''
        article_item = JoBoleArticleItem()



        front_image_url = response.meta.get("front_image_url","")  #文章封面图地址
        title = response.xpath('//div[@class="entry-header"]/h1/text()').extract_first()


        create_date = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/text()').extract()[0].strip().split()[0]

        tag_list = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a/text()').extract()
        tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
        tag =",".join(tag_list)
        praise_nums = response.xpath('//span[contains(@class,"vote-post-up")]/h10/text()').extract()
        if len(praise_nums) == 0:
            praise_nums = 0
        else:
            praise_nums = int(praise_nums[0])
        fav_nums  = response.xpath('//span[contains(@class,"bookmark-btn")]/text()').extract()[0]
        match_re = re.match(".*(\d+).*",fav_nums)
        if match_re:
            fav_nums = int(match_re.group(1))
        else:
            fav_nums = 0

        comment_nums =response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
        match_com = re.match(".*(\d+).*",comment_nums)
        if match_com:
            comment_nums = int(match_com.group(1))
        else:
            comment_nums=0

        content = response.xpath('//div[@class="entry"]').extract()[0]


        article_item["url_object_id"] = get_md5(response.url) #这里对地址进行了md5变成定长
        article_item["title"] = title
        article_item["url"] = response.url
        try:
            create_date = datetime.datetime.strptime(create_date,'%Y/%m/%d').date()
        except Exception as e:
            create_date = datetime.datetime.now().date()

        article_item["create_date"] = create_date
        article_item["front_image_url"] = [front_image_url]
        article_item["praise_nums"] = int(praise_nums)
        article_item["fav_nums"] = fav_nums
        article_item["comment_nums"] = comment_nums
        article_item["tag"] = tag
        article_item['content'] = content

        yield article_item

pipeline中代码分析 pipeline主要是对spiders中爬虫的返回数据处理。

class JobbolespiderPipeline(object):
    def process_item(self, item, spider):
        return item

class JsonWithEncodingPipeline(object):
    '''
    返回json数据到文件
    '''
    def __init__(self):
        self.file = codecs.open("article.json",'w',encoding="utf-8")

    def process_item(self, item, spider):
        lines = json.dumps(dict(item),ensure_ascii=False) + "\n"
        self.file.write(lines)
        return item

    def spider_closed(self,spider):
        self.file.close()


class MysqlPipeline(object):
    '''
    插入mysql数据库
    '''
    def __init__(self):
        self.conn =pymysql.connect(host='192.168.1.19',port=3306,user='root',passwd='123456',db='article_spider',use_unicode=True, charset="utf8")
        self.cursor = self.conn.cursor()

    def process_item(self,item,spider):
        insert_sql = '''
        insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tag,content) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
        '''

        self.cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["front_image_path"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tag"],item["content"]))
        self.conn.commit()


class MysqlTwistedPipline(object):
    '''
    采用异步的方式插入数据
    '''
    def __init__(self,dbpool):
        self.dbpool = dbpool

    @classmethod
    def from_settings(cls,settings):
        dbparms = dict(
            host = settings["MYSQL_HOST"],
            port = settings["MYSQL_PORT"],
            user = settings["MYSQL_USER"],
            passwd = settings["MYSQL_PASSWD"],
            db = settings["MYSQL_DB"],
            use_unicode = True,
            charset="utf8",
        )
        dbpool = adbapi.ConnectionPool("pymysql",**dbparms)
        return cls(dbpool)
    def process_item(self,item,spider):
        '''
        使用twisted将mysql插入变成异步
        :param item:
        :param spider:
        :return:
        '''
        query = self.dbpool.runInteraction(self.do_insert,item)
        query.addErrback(self.handle_error)

    def handle_error(self,failure):
        #处理异步插入的异常
        print(failure)

    def do_insert(self,cursor,item):
        #具体插入数据
        insert_sql = '''
        insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tag,content) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
        '''
        cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["front_image_path"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tag"],item["content"]))



class ArticleImagePipeline(ImagesPipeline):
    '''
    对图片的处理
    '''
    def item_completed(self, results, item, info):

        for ok ,value in results:
            if ok:
                image_file_path = value["path"]
                item['front_image_path'] = image_file_path
            else:
                item['front_image_path'] = ""


        return item

Scrapy框架的架构和原理

scrapy数据流是由执行的核心引擎控制,流程如下:

  1. 爬虫引擎ENGINE获得初始请求开始抓取。
  2. 爬虫引擎ENGINE开始请求调度程序SCHEDULER,并准备对下一次的请求进行抓取。
  3. 爬虫调度器返回下一个请求给爬虫引擎。
  4. 引擎请求发送到下载器DOWNLOADER,通过下载中间件下载网络数据。
  5. 一旦下载器完成页面下载,将下载结果返回给爬虫引擎ENGINE。
  6. 爬虫引擎ENGINE将下载器DOWNLOADER的响应通过中间件MIDDLEWARES返回给爬虫SPIDERS进行处理。
  7. 爬虫SPIDERS处理响应,并通过中间件MIDDLEWARES返回处理后的items,以及新的请求给引擎。
  8. 引擎发送处理后的items到项目管道,然后把处理结果返回给调度器SCHEDULER,调度器计划处理下一个请求抓取。
  9. 重复该过程(继续步骤1),直到爬取完所有的url请求。

各组件介绍

  • 爬虫引擎 爬虫引擎负责控制各个组件之间的数据流,当某些操作触发事件后都是通过engine来处理。
  • 调度器 调度接收来engine的请求并将请求放入队列中,并通过事件返回给engine。
  • 下载器 通过engine请求下载网络数据并将结果响应给engine。
  • Spider Spider发出请求,并处理engine返回给它下载器响应数据,以items和规则内的数据请求(urls)返回给engine。
  • 管道项目 负责处理engine返回spider解析后的数据,并且将数据持久化,例如将数据存入数据库或者文件。
  • 下载中间件 下载中间件是engine和下载器交互组件,以钩子(插件)的形式存在,可以代替接收请求、处理数据的下载以及将结果响应给engine。
  • spider中间件 spider中间件是engine和spider之间的交互组件,以钩子(插件)的形式存在,可以代替处理response以及返回给engine items及新的请求集。

Scrapy框架中Spiders用法

我们所有自己写的爬虫都是继承与spider.Spider这个类

name 定义爬虫名字,我们通过命令启动的时候用的就是这个名字,这个名字必须是唯一的

allowed_domains 包含了spider允许爬取的域名列表。当offsiteMiddleware启用时,域名不在列表中URL不会被访问 所以在爬虫文件中,每次生成Request请求时都会进行和这里的域名进行判断

start_urls 起始的url列表 这里会通过spider.Spider方法中会调用start_request循环请求这个列表中每个地址。

custom_settings 自定义配置,可以覆盖settings的配置,主要用于当我们对爬虫有特定需求设置的时候 设置的是以字典的方式设置:custom_settings = {}

from_crawler 这是一个类方法,我们定义这样一个类方法,可以通过crawler.settings.get()这种方式获取settings配置文件中的信息,同时这个也可以在pipeline中使用

start_requests() 这个方法必须返回一个可迭代对象,该对象包含了spider用于爬取的第一个Request请求 这个方法是在被继承的父类中spider.Spider中写的,默认是通过get请求,如果我们需要修改最开始的这个请求,可以重写这个方法,如我们想通过post请求

make_requests_from_url(url) 这个也是在父类中start_requests调用的,当然这个方法我们也可以重写

parse(response) 这个其实默认的回调函数 负责处理response并返回处理的数据以及跟进的url 该方法以及其他的Request回调函数必须返回一个包含Request或Item的可迭代对象

Scrapy矿建中Item Pipeline用法

当Item 在Spider中被收集之后,就会被传递到Item Pipeline中进行处理

每个item pipeline组件是实现了简单的方法的python类,负责接收到item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或者被丢弃而不再进行处理

item pipeline的主要作用:

  • 清理html数据
  • 验证爬取的数据
  • 去重并丢弃
  • 讲爬取的结果保存到数据库中或文件中

编写自己的item pipeline

每个item piple组件是一个独立的pyhton类,必须实现以process_item(self,item,spider)方法 每个item pipeline组件都需要调用该方法,这个方法必须返回一个具有数据的dict,或者item对象,或者抛出DropItem异常,被丢弃的item将不会被之后的pipeline组件所处理

下面的方法也可以选择实现

open_spider(self,spider) 表示当spider被开启的时候调用这个方法

close_spider(self,spider) 当spider挂去年比时候这个方法被调用

from_crawler(cls,crawler) 这个和我们在前面说spider的时候的用法是一样的,可以用于获取settings配置文件中的信息,需要注意的这个是一个类方法

启用一个item pipeline组件

在settings配置文件汇总有一个ITEM_PIPELINES的配置参数,如下:

ITEM_PIPELINES = {
    'myproject.pipelines.PricePipeline': 300,
    'myproject.pipelines.JsonWriterPipeline': 800,
}

每个pipeline后面有一个数值,这个值的范围是0-1000,这个数值决定了他们的执行顺序,数字越小越优先。

Scrapy框架中Download Middleware用法

Downloader Middleware处理的过程主要在调度器发送requests请求的时候以及网页将response结果返回给spiders的时候,所以从这里我们可以知道下载中间件是介于Scrapy的request/response处理的钩子,用于修改Scrapy request和response。

在我们的爬虫中,如果要伪装自己的IP,防止网站封IP,可以在middlewares.py中写如下的中间件类:

class ProxyMiddleare(object):
    logger = logging.getLogger(__name__)
    def process_request(self,request, spider):
        self.logger.debug("Using Proxy")
        request.meta['proxy'] = 'http://127.0.0.1:9743'
        return None

然后在settings.py配置文件中开启下载中间件功能:

DOWNLOADER_MIDDLEWARES={
    'httpbintest.middlewares.ProxyMiddleware':543,
}

middleware中的方法说明

process_request(request,spider)

当每个request通过下载中间件时,该方法被调用,这里有一个要求,该方法必须返回以下三种中的任意一种:None,返回一个Response对象,返回一个Request对象或raise IgnoreRequest。三种返回值的作用是不同的。

  • None:Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用,该request被执行(其response被下载)。

  • Response对象:Scrapy将不会调用任何其他的process_request()或process_exception() 方法,或相应地下载函数;其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。

  • Request对象:Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。

  • raise一个IgnoreRequest异常:则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录。

process_response(request, response, spider)

process_response的返回值也是有三种:response对象,request对象,或者raise一个IgnoreRequest异常

  • 如果其返回一个Response(可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。

  • 如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。

  • 如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。

process_exception(request, exception, spider)

当下载处理器(download handler)或 process_request() (下载中间件)抛出异常(包括 IgnoreRequest 异常)时,Scrapy调用 process_exception()。

process_exception() 也是返回三者中的一个: 返回 None 、 一个 Response 对象、或者一个 Request 对象。

  • 如果其返回 None ,Scrapy将会继续处理该异常,接着调用已安装的其他中间件的 process_exception() 方法,直到所有中间件都被调用完毕,则调用默认的异常处理。

  • 如果其返回一个 Response 对象,则已安装的中间件链的 process_response() 方法被调用。Scrapy将不会调用任何其他中间件的 process_exception() 方法。

  • 如果其返回一个 Request 对象, 则返回的request将会被重新调用下载。这将停止中间件的 process_exception() 方法执行,就如返回一个response的那样。 这个是非常有用的,就相当于如果我们失败了可以在这里进行一次失败的重试,例如当我们访问一个网站出现因为频繁爬取被封ip就可以在这里设置增加代理继续访问

使用middleware 实现随机更换user-agent

from scrapy import signals
class UserAgentMiddleware(object):
    """This middleware allows spiders to override the user_agent"""

    def __init__(self, user_agent='Scrapy'):
        self.user_agent = user_agent

    @classmethod
    def from_crawler(cls, crawler):
        o = cls(crawler.settings['USER_AGENT'])
        crawler.signals.connect(o.spider_opened, signal=signals.spider_opened)
        return o

    def spider_opened(self, spider):
        self.user_agent = getattr(spider, 'user_agent', self.user_agent)

    def process_request(self, request, spider):
        if self.user_agent:
            request.headers.setdefault(b'User-Agent', self.user_agent)

在settings配置文件中配置:

DOWNLOADER_MIDDLEWARES = {
    'jobboleSpider.middlewares.RandomUserAgentMiddleware': 543,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}

RANDOM_UA_TYPE= 'random'

这里我们要将系统的UserAgent中间件设置为None,这样就不会启用,否则默认系统的这个中间会被启用 定义RANDOM_UA_TYPE这个是设置一个默认的值

关于随机切换user-agent的库

github地址为:https://github.com/hellysmile/fake-useragent 安装:pip install fake-useragent

使用例子:

from fake_useragent import UserAgent

ua = UserAgent()

print(ua.ie)
print(ua.chrome)
print(ua.Firefox)
print(ua.random)
print(ua.random)
print(ua.random)

利用规则实现多页面抓取

从start_urls开始,对多页面抓取,可以利用scrapy的规则匹配来自动实现:

# coding=utf-8
__author__ = 'Jeffee Chen'

from scrapy.contrib.spiders import CrawlSpider, Rule
from douban.items import DoubanItem
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor

class GroupSpider(CrawlSpider):
    name = "Douban"
    allowed_domains = ["douban.com"]
    start_urls = ["http://www.douban.com/group/explore?tag=%E7%A4%BE%E7%A7%91"]

    rules = [
        Rule(SgmlLinkExtractor(allow=(r'/group/explore\?start=.*', )), callback='parse_next_page'),
    ]

    def parse_next_page(self, response):
        item = DoubanItem()
        sel = response.xpath("//div[@class='group-list']/div[@class='result']")
        for s in sel:
            info = s.xpath("div/div/h3/a/text()").extract()
            item["groupName"] = info
            yield item

官方文档对rules的解析:

class scrapy.contrib.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)

  • link_extractor: 是一个 Link Extractor 对象。 其定义了如何从爬取到的页面提取链接。
  • callback: 是一个callable或string(该spider中同名的函数将会被调用)。 从link_extractor中每获取到链接时将会调用该函数。该回调函数接受一个response作为其第一个参数, 并返回一个包含 Item 以及(或) Request 对象(或者这两者的子类)的列表(list)。
  • cb_kwargs: 包含传递给回调函数的参数(keyword argument)的字典。
  • follow: 是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果 callback 为None, follow 默认设置为 True ,否则默认为 False 。
  • process_links: 是一个callable或string(该spider中同名的函数将会被调用)。 从link_extractor中获取到链接列表时将会调用该函数。该方法主要用来过滤。
  • process_request: 是一个callable或string(该spider中同名的函数将会被调用)。 该规则提取到每个request时都会调用该函数。该函数必须返回一个request或者None。 (用来过滤request)

从论坛里根据时间过滤,抓取每天更新的内容(不使用rules)- 多页抓取

使用rules抓取多页内容,鱿鱼rule进行url提取后只保留了连接地址,失去了上下文内息,不适用这个场景。

转载于:https://my.oschina.net/ez8life/blog/1925621

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值