ios基础-5-选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//
// main.m
// 5选择if
//
// Created by wanggang on 2017/7/24.
// Copyright © 2017年 hackingangle. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
// if
if (a > 1) {
NSLog(@"a > 1");
}
// if-else
if (a > 10) {
NSLog(@"a > 10");
} else {
NSLog(@"a < 10");
}
// 嵌套
if (a != 0) {
if (a > 0) {
NSLog(@"a > 0");
} else {
NSLog(@"a < 0");
}
}
// if-else if-else
if (a < 0) {
NSLog(@"a < 0");
} else if (a > 0) {
NSLog(@"a > 0");
} else {
NSLog(@"a = 0");
}
}
return 0;
}

ios基础-4-运算符

  1. 所有的数字的计算都离不开

    • +
    • -
    • *
    • /
  2. 存在一些逻辑上的关系,比如且,或,非

    • &&
    • ||
    • !
  3. 取模为一种经常使用的行为

    • 1%10
      • =1
  4. 赋值的时候进行计算

    • +=
  5. 自增、自减运算

    • ++
    • --
  6. 位运算

    • a&1
      • =1
    • a<<1
      • *2
    • a>>1
      • /2
  7. 三目

    • (a>b)?1:0

linux-tcp

tcp

查看timeout数量

1
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

显示结果

1
2
3
4
5
SYN_RECV 1
CLOSE_WAIT 1
ESTABLISHED 122
FIN_WAIT2 3
TIME_WAIT 55745

scrapy-itemloader

what

上述的item中每个属性的提取规则定义在爬虫中了,不方便进行代码的复用和管理。

how

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# -*- coding: utf-8 -*-
import scrapy
import re
from scrapy.http import Request
from urllib import parse
from ArticleSpider.items import JobBoleArticleItem
from ArticleSpider.utils.common import get_md5
from scrapy.loader import ItemLoader
from ArticleSpider.items import ArticleItemLoader
class JobboleSpider(scrapy.Spider):
# 爬虫名称
name = 'jobbole'
# url爬取域名白名单
allowed_domains = ['blog.jobbole.com']
# 开启爬取url列表
start_urls = ['http://blog.jobbole.com/all-posts/']
def parse(self, response):
articleUrlHandles = response.css("#archive .floated-thumb .post-thumb")
for articleUrlHandle in articleUrlHandles:
articleUrl = articleUrlHandle.css("a::attr(href)").extract_first("")
articleBanner = articleUrlHandle.css("a img::attr(src)").extract_first("")
yield Request(url=articleUrl, callback=self.parse_detail, meta={"front_image_url":parse.urljoin(response.url, articleBanner)})
nextUrl = response.css("a.next.page-numbers::attr(href)").extract_first("")
if nextUrl:
yield Request(url=parse.urljoin(response.url, nextUrl), callback=self.parse)
return
def parse_detail(self, response):
# 实例化JobBoleArticleItem
# article_item = JobBoleArticleItem()
# meta获取banner
front_image_url = response.meta.get("front_image_url", "")
# # 提取
# title = response.xpath('//div[@class="entry-header"]/h1/text()')
# # 解压缩转数组
# titleArr = title.extract()
# # 提取第一个为标题
# ret_title = titleArr[0]
# # 提取创建日志
# ret_create_date = response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract()[0].strip().replace(
# " ·", "")
# # 提取点赞
# ret_praise_nums = int(response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0])
# # 收藏
# strFavNums = response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0]
# reHandle = re.match(".*(\d+).*", strFavNums)
# if reHandle:
# ret_fav_nums = int(reHandle.group(1))
# else:
# ret_fav_nums = 0
# # 评论
# strCommentNums = response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
# reHandle = re.match(".*(\d+).*", strCommentNums)
# if reHandle:
# ret_comment_nums = int(reHandle.group(1))
# else:
# ret_comment_nums = 0
# # 正文
# ret_content = response.xpath("//div[@class='entry']/text()").extract()[0]
#
# # 标签
# tagList = response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract()
# tagList = [element for element in tagList if not element.strip().endswith("评论")]
# ret_tags = ",".join(tagList)
# 设定jobboleItem
# article_item['title'] = ret_title
# article_item['create_date'] = ret_create_date
# article_item['url'] = response.url
# article_item['url_object_id'] = get_md5(response.url)
# article_item['front_image_url'] = [front_image_url]
# article_item['praise_nums'] = ret_praise_nums
# article_item['comment_nums'] = ret_comment_nums
# article_item['fav_nums'] = ret_fav_nums
# article_item['tags'] = ret_tags
# article_item['content'] = ret_content
# yield article_item
item_loader = ArticleItemLoader(item=JobBoleArticleItem(), response=response)
item_loader.add_xpath("title", '//div[@class="entry-header"]/h1/text()')
item_loader.add_xpath("create_date", "//p[@class='entry-meta-hide-on-mobile']/text()")
item_loader.add_xpath("praise_nums", "//span[contains(@class, 'vote-post-up')]/h10/text()")
item_loader.add_xpath("fav_nums", "//span[contains(@class, 'bookmark-btn')]/text()")
item_loader.add_xpath("comment_nums", "//a[@href='#article-comment']/span/text()")
item_loader.add_xpath("tags", "//p[@class='entry-meta-hide-on-mobile']/a/text()")
item_loader.add_xpath("content", "//div[@class='entry']/text()")
item_loader.add_value("url", response.url)
item_loader.add_value("url_object_id", get_md5(response.url))
item_loader.add_value("front_image_url", [front_image_url])
article_item = item_loader.load_item()
yield article_item
pass

items.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
import datetime
import re
import scrapy
from scrapy.loader.processors import MapCompose,TakeFirst,Join
from scrapy.loader import ItemLoader
class ArticlespiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
def add_jobbole(value):
return value + "-jobbole"
def convert_date(value):
try:
create_date = datetime.datetime.strptime(value, '%Y/%m/%d').date()
except Exception as e:
create_date = datetime.datetime.now().date()
return create_date
def get_nums(value):
reHandle = re.match(".*(\d+).*", value)
if reHandle:
ret_comment_nums = int(reHandle.group(1))
else:
ret_comment_nums = 0
return ret_comment_nums
def remove_comment_tags(value):
if "评论" in value:
return ""
else:
return value
def return_value(value):
return value
class ArticleItemLoader(ItemLoader):
default_output_processor = TakeFirst()
class JobBoleArticleItem(scrapy.Item):
title = scrapy.Field(
input_processor = MapCompose(
add_jobbole,
lambda x:x+"-AGAIN"
)
)
create_date = scrapy.Field(
input_processor = MapCompose(
convert_date
),
output_processor = TakeFirst()
)
url = scrapy.Field()
# url md5
url_object_id = scrapy.Field()
# 封面图
front_image_url = scrapy.Field(
output_processor = MapCompose(return_value)
)
# 封面图本地下载后路径
front_image_path = scrapy.Field()
# 点赞
praise_nums = scrapy.Field(
input_processor = MapCompose(
get_nums
)
)
# 评论
comment_nums = scrapy.Field(
input_processor = MapCompose(
get_nums
)
)
# 收藏
fav_nums = scrapy.Field(
input_processor = MapCompose(
get_nums
)
)
# 标签
tags = scrapy.Field(
# input_processor = MapCompose(remove_comment_tags),
output_processor = Join(",")
)
content = scrapy.Field()
pass

可以加工title做一些转化操作,必须引入MapCompose类指定加载顺序

output_processor指定了输出属性的提取规则,通过scrapy提供的TakeFirst()我们可以默认提取list中的第一个元素

另外,scrapy同时也提供了Join方法来默认支持拼接

我们要为每个属性都指定output_processor吗,当然不!因为这样太大的代码修改了,不符合我们软件工程中的复用的思想,这里我们通过修改默认的output_processor的行为来搞定

1
2
3
from scrapy.loader import ItemLoader
class ArticleItemLoader(ItemLoader):
default_output_processor = TakeFirst()

知识点

  • 3个提取指定方法
    • item_loader.add_css()
    • item_loader.add_xpath()
    • item_loader.add_value()
  • 匿名函数
    • lambda x:x+'-demo'

scrapy-pipeline保存到数据库

我们介绍了python环境依赖管理virtual安装、scrapy安装、xpath、css提取方法、items、pipeline、ImagePipeline自定义,
通过上述的知识,我们可以把网页中的信息提取出来,组装成items,然后把items流通在一个个数据管道里面,今天我们说的就是发生在
某一个数据管道里面的事情,把items数据做持久化处理:

  1. 保存本地json文件
  2. 保存到mysql数据库表中

保存本地jsonhow

自定义JsonPipeline

1
2
3
4
5
6
7
8
9
class JsonWithEncodingPipeline(object):
def __init__(self):
self.file = codecs.open("articles.json", "w", encoding="utf-8")
def process_item(self, item, spider):
lines = json.dumps(dict(item), ensure_ascii=False)
self.file.write(lines)
return item
def spider_closed(self, spider):
self.file.close()

ensure_ascii=False保证utf8字符不乱码

settings.py

1
2
3
4
5
ITEM_PIPELINES = {
'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
# 'scrapy.pipelines.images.ImagesPipeline': 11,
}

scrapy.exporters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from scrapy.exporters import JsonItemExporter
class JsonExporterPipeline(object):
def __init__(self):
self.file = open('articleexporter.json', 'wb')
self.exporter = JsonItemExporter(self.file, encoding="utf8", ensure_ascii=False)
self.exporter.start_exporting()
def close_spider(self, spider):
self.exporter.finish_exporting()
self.file.close()
def process_item(self, item, spider):
self.exporter.export_item(item)
return item

ensure_ascii=False保证utf8字符不乱码

settings.py

1
2
3
4
5
ITEM_PIPELINES = {
'ArticleSpider.pipelines.JsonExporterPipeline': 2,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
# 'scrapy.pipelines.images.ImagesPipeline': 11,
}

mysql保存

设计数据库表

laravel 中 migrations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateArticlesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->string('title', 200);
$table->date('create_date')->nullable();
$table->string('url', 300);
$table->string('url_object_id', 50);
$table->string('front_image_url', 300)->nullable();
$table->string('front_image_path', 200)->nullable();
$table->integer('comment_nums')->nullable();
$table->integer('fav_nums')->nullable();
$table->integer('praise_nums')->nullable();
$table->string('tags', 200)->nullable();
$table->longText('content')->nullable();
$table->primary('url_object_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('articles');
}
}

安装mysql驱动

1
2
workon scrapy_python3
pip install mysqlclient

ubuntu下报错,需要安装

1
sudo apt-get install libmysqlclient-dev

centos下报错,需要安装

1
sudo yum install python-devel mysql-devel

同步插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import MySQLdb
class MysqlPipeline(object):
def __init__(self):
host = "127.0.0.1"
user = "homestead"
password = "secret"
dbname = "jobbole"
port = 33060
self.conn = MySQLdb.connect(host, user, password, dbname, port=port, charset="utf8", use_unicode=True)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
sql = """
insert into articles(title, url, url_object_id, create_date, fav_nums)
VALUES (%s, %s, %s, %s, %s)
"""
self.cursor.execute(sql, (item["title"], item["url"], item["url_object_id"], item["create_date"], item["fav_nums"]))
self.conn.commit()
return item

settings.py

1
2
3
4
5
ITEM_PIPELINES = {
'ArticleSpider.pipelines.MysqlPipeline': 2,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
# 'scrapy.pipelines.images.ImagesPipeline': 11,
}

异步插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import MySQLdb
import MySQLdb.cursors
# twisted提供了异步容器,底层还是MYSQLdb
class MysqlTwistedPipeline(object):
def __init__(self, dbpool):
self.dbpool = dbpool
@classmethod
# 被scrapy调用的hook,传递settings.py值进入
def from_settings(cls, settings):
dbparams = dict(
host = settings["MYSQL_HOST"],
user = settings["MYSQL_USER"],
password = settings["MYSQL_PASSWORD"],
db = settings["MYSQL_DBNAME"],
port = settings["MYSQL_PORT"],
charset = "utf8",
cursorclass = MySQLdb.cursors.DictCursor,
use_unicode = True,
)
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparams)
return cls(dbpool)
# 异步操作异常需要单独处理
def process_item(self, item, spider):
query = self.dbpool.runInteraction(self.doMysqlTwistedPipeline, item)
# 处理异常
query.addErrback(self.handle_errors, item, spider)
def handle_errors(self, failure, item, spider):
print("------------------------------error==========================>", failure)
def doMysqlTwistedPipeline(self, cursor, item):
sql = """
insert into articles(title, url, url_object_id, create_date, fav_nums)
VALUES (%s, %s, %s, %s, %s)
"""
cursor.execute(sql, (item["title"], item["url"], item["url_object_id"], item["create_date"], item["fav_nums"]))

settings.py

1
2
3
4
5
6
7
8
9
10
11
ITEM_PIPELINES = {
'ArticleSpider.pipelines.MysqlTwistedPipeline': 2,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
# 'scrapy.pipelines.images.ImagesPipeline': 11,
}
# mysql confs
MYSQL_HOST = "127.0.0.1"
MYSQL_USER = "homestead"
MYSQL_PASSWORD = "secret"
MYSQL_DBNAME = "jobbole"
MYSQL_PORT = 33060

知识点

  1. 打开文件

    1
    2
    3
    import codecs
    file = codecs.open('article.json', 'w', encoding='utf-8')
    file.write(lines)
  2. items转dict转json

    1
    2
    import json
    json.dumps(dict(items), ensure_ascii=False)
  3. 识别spider关闭事件来处理关闭连接

    1
    2
    def spider_closed(self, spider):
    self.file.close()
  4. 二进制方式打开文件

    1
    file = open("xx.json", "wb")
  5. 日期转换

    1
    2
    3
    4
    5
    import datetime
    try:
    create_date = datetime.datetime.strptime(create_date, "%Y/%m/%d").date()
    except Exception as e:
    create_date = datetime.datetime.now().date()
  6. md5后字符长度为50

  7. 可变化参数
    • **dbparams

scrapy-items&pipelines

what

数据库dao对象,可以不指定类别定义对象属性,为了保存到数据库提供便利

定义

items.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class JobBoleArticleItem(scrapy.Item):
title = scrapy.Field()
create_date = scrapy.Field()
url = scrapy.Field()
# url md5
url_object_id = scrapy.Field()
# 封面图
front_image_url = scrapy.Field()
# 封面图本地下载后路径
front_image_path = scrapy.Field()
# 点赞
praise_nums = scrapy.Field()
# 评论
comment_nums = scrapy.Field()
# 收藏
fav_nums = scrapy.Field()
# 标签
tags = scrapy.Field()
content = scrapy.Field()
pass

使用

在爬虫程序中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from ArticleSpider.items import JobBoleArticleItem
# 实例化JobBoleArticleItem
article_item = JobBoleArticleItem()
# 设定jobboleItem
article_item['title'] = ret_title
article_item['create_date'] = ret_create_date
article_item['url'] = response.url
article_item['front_image_url'] = front_image_url
article_item['praise_nums'] = ret_praise_nums
article_item['comment_nums'] = ret_comment_nums
article_item['fav_nums'] = ret_fav_nums
article_item['tags'] = ret_tags
article_item['content'] = ret_content
# 交付pipeline
yield article_item

yield article_item这里是重点中的重点,再来回顾一下:

  • 如果yield Request把一个url交付到Scrapy中下载分析;
  • 如果yield XX_item是把item交付到pipeline中

pipelines

pipelines收到yield的items,然后处理,如果要开启pipelines,也需要设置

settings.py

1
2
3
4
# 开启注释
ITEM_PIPELINES = {
'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
}

这时,我们的默认的一个pipelineArticlespiderPipeline就被激活了,断点可以打到这里

pipeline调试

pipelines之自动下载图片

pipelines是各种功能管道,用来处理数据对象items的各个属性,接下来,我们来分析一种下载图片的pipeline

settings.py开启设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os
# 开启注释
ITEM_PIPELINES = {
'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
'scrapy.pipelines.images.ImagesPipeline': 1,
}
# 图片url字段,位于item中
IMAGES_URLS_FIELD = "front_image_url"
# 项目目录
project_dir = os.path.dirname(os.path.abspath(__file__))
# 图片存储的目录
IMAGES_STORE = os.path.join(project_dir, "images")
# 图片最小宽度
IMAGES_MIN_WIDTH = 100
# 图片最小高度
IMAGES_MIN_HEIGHT = 100

执行后,发现缺少库PIL,则切到环境安装:

1
2
workon scrapy_pyth
pip install pillow

还是执行不了,提示异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Traceback (most recent call last):
File "/Users/wanggang/.virtualenvs/scrapy_python3/lib/python3.6/site-packages/twisted/internet/defer.py", line 653, in _runCallbacks
current.result = callback(current.result, *args, **kw)
File "/Users/wanggang/.virtualenvs/scrapy_python3/lib/python3.6/site-packages/scrapy/pipelines/media.py", line 79, in process_item
requests = arg_to_iter(self.get_media_requests(item, info))
File "/Users/wanggang/.virtualenvs/scrapy_python3/lib/python3.6/site-packages/scrapy/pipelines/images.py", line 152, in get_media_requests
return [Request(x) for x in item.get(self.images_urls_field, [])]
File "/Users/wanggang/.virtualenvs/scrapy_python3/lib/python3.6/site-packages/scrapy/pipelines/images.py", line 152, in <listcomp>
return [Request(x) for x in item.get(self.images_urls_field, [])]
File "/Users/wanggang/.virtualenvs/scrapy_python3/lib/python3.6/site-packages/scrapy/http/request/__init__.py", line 25, in __init__
self._set_url(url)
File "/Users/wanggang/.virtualenvs/scrapy_python3/lib/python3.6/site-packages/scrapy/http/request/__init__.py", line 58, in _set_url
raise ValueError('Missing scheme in request url: %s' % self._url)
ValueError: Missing scheme in request url: h

为什么呢?看异常问题出在处理图片下载的pipeline上面,我们可以看到在settings.py中设置了front_image_url属性为
items中的下载图片的属性,但是有一个问题,默认的ImagesPipeline肯定不想只是下载一个图,所以他默认会把这个属性当成[]处理,
所以我们在初始化的时候需要做如下:

1
article_item['front_image_url'] = [front_image_url]

imagesPipelines成功下图

定制化ImagePipeline

太赞了,我们通过ImagePipeline已经可以实现图片的自动下载了,但是,但是,我想说的是但是,但是不要骄傲

因为本地路径还没有保存到item中呢,我们如何实现?

ImagePipeline封装在scrapy提供的第三方包中,我们一定不能直接修改源代码,要通过继承的方式拓展开来

这样,我们就再定一个Pipeline来实现本地图片下载路径写入到item中的需求吧

pipelines.py创建新的ArticleImagePipeline

1
2
3
4
5
6
class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
for ok, value in results:
image_file_path = value["path"]
item["front_image_path"] = image_file_path
return item

修改优先级settings.py

1
2
3
4
5
ITEM_PIPELINES = {
'ArticleSpider.pipelines.ArticlespiderPipeline': 2,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
# 'scrapy.pipelines.images.ImagesPipeline': 11,
}

md5

python中md5如何计算,需要自己定义!

创建一个工具package:utils

1
2
3
4
5
6
7
8
9
import hashlib
def get_md5(url):
m = hashlib.md5()
m.update(url)
return m.hexdigest()
if __name__ == "__main__":
print(get_md5("http://www.baidu.com"))

执行报错如下:

1
2
3
4
5
6
7
8
9
/Users/wanggang/.virtualenvs/scrapy_python3/bin/python3.6 /Users/wanggang/scrapy/ArticleSpider/ArticleSpider/utils/common.py
Traceback (most recent call last):
File "/Users/wanggang/scrapy/ArticleSpider/ArticleSpider/utils/common.py", line 9, in <module>
print(get_md5("http://www.baidu.com"))
File "/Users/wanggang/scrapy/ArticleSpider/ArticleSpider/utils/common.py", line 5, in get_md5
m.update(url)
TypeError: Unicode-objects must be encoded before hashing
Process finished with exit code 1

报错原因很简单,因为在python3中,所有的字符串自动认为是unicode的,所以,我们需要转utf8

1
"http://www.baidu.com".encode("utf-8")

当然,我们可以让程序更健壮些:

1
2
3
4
5
6
7
8
9
10
11
import hashlib
def get_md5(url):
if isinstance(url, str):
url = url.encode("utf-8")
m = hashlib.md5()
m.update(url)
return m.hexdigest()
if __name__ == "__main__":
print(get_md5("http://www.baidu.com"))

知识点

  1. Request可以携带数据到下一个请求的结果对象中
    • 参数代入数据
      • Request(meta={"front_image_url":front_image_url})
    • 使用
      • 可能溢出
        • response.meta["front_image_url"]
      • 防溢出
        • response.meta.get("front_image_url", "")
  2. os.path.dirname
  3. os.path.join(project_dir, “images”)
  4. os.path.abspath(file)
  5. file
  6. scrapy.pipelines.images.ImagesPipeline
    • IMAGES_URLS_FIELD
      • 图片url字段,位于item中
    • IMAGES_STORE
      • 图片存储的目录
  7. tuple解包

scrapy-jobbole

开始爬取之前

在开始爬取之前,我们只是知道如何定义开始爬取的页面地址,然后分析这个页面里面的核心元素,使用xpath或者css提取器

现在让我们都学一些,如何爬取到所有的jobbole中的文章呢,这里介绍的是通过列表页面爬取的方式

我们没有介绍:

  1. 如何从列表开始页面分析到每一个资源的地址
  2. 将这个地址告诉给scrapy的下载器,通过scrapy下载器来下载、分析
  3. 提取下一页链接,传递到scrapy处理

处理url请求

1
2
3
4
5
6
7
8
# python3
from urllib import parse
# python2
# import urlparse
# 第一个参数是带有主域名的任意url,第二个参数是uri
parse.urljoin(response.url, nextUrl)

构建请求

  1. 提取所有资源链接

    1
    articleUrls = response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()
  2. 提取到资源url,发送到scrapy下载分析

    1
    2
    from scrapy.http import Request
    yield Request(url=articleUrl, callback=self.parse_detail)

其中callback指定的self.parse_detail是设定的回调方法,用来对这个请求的返回结果的处理回调

  1. 提取到下一页,发送到scrapy下载分析
    1
    2
    3
    nextUrl = response.css("a.next.page-numbers::attr(href)").extract_first("")
    if nextUrl:
    yield Request(url=parse.urljoin(response.url, nextUrl), callback=self.parse)

知识点

python

  • 强转
    • int(XX)

scrapy-css选择器

what

css定义了html中的样式,是一个网页中必不可少的元素。

css选择器用来定位某一个html节点,与xpath作用相同。

语法

  • *
    • 选择所有节点
  • #container
    • id为container节点
  • .container
    • class包含container节点
  • li a
    • 所有li下所有a节点
  • ul + p
    • ul后第一个p
  • div#cotainer > ul
    • id为container节点的div的第一个ul子元素
  • ul ~ p
    • 与ul相邻的所有p元素
  • a[title]
    • 有title属性a元素
  • a[href=”http://jobbole.com“]
  • a[href*=”jobbole”]
    • href包含jobbole的a元素
  • a[href^=”http”]
    • 以http开头的a元素
  • a[href$=”.jpg”]
    • 以.jpg结尾的a元素
  • input[type=radio]:checked
    • 选择的radio元素
  • div:not(#container)
    • id不等于container的div元素
  • li:nth-child(3)
    • 第三个li元素
  • tr:nth-child(2n)
    • 偶数tr

how

scrapy shell

进入shell模式

1
scrapy shell http://blog.jobbole.com/111742/

交互下调试:

1
2
3
4
# 标题
ret_title = response.css(".entry-header h1::text").extract()[0]
# 创建时间
ret_create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace(" ·", "")
  • 知识点
    • extract_first(“”)
      • 如果list第一个元素为空,则返回””
    • .floated-thumb .post-thumb a::attr(href)

scrapy-xpath

what

  • xpath使用路径表达式在xml和html进行导航
  • xpath包含标准函数库
  • xpath是w3c标准

节点关系

  • 父节点
    • html是head父节点
  • 子节点
    • head是html子节点
  • 同袍节点
    • head,body
  • 先辈节点
    • meta先辈节点:head,html
  • 后代节点
    • meat是head,html的后代节点

语法

  • article
    • 所有article 所有子节点
  • /article
    • 根元素 article
  • article/a
    • 所有article 子元素a元素
  • //div
    • 所有div 所有子节点(无论在文档中任何位置)
  • article//div
    • 所有article 后代div无论位置
  • //@class
    • 所有class的属性
  • /article/div[1]
    • article子元素第一个div
  • /article/div[last()]
    • article子元素最后一个div
  • /article/div[last()-1]
    • article子元素倒数第二个div
  • //div[@lang]
    • 所有有lang属性的div
  • //div[@lang=’eng’]
    • 所有lang属性等于eng的div
  • //div/*
    • 属于div元素所有子节点
  • //*
    • 所有元素
  • //div[@*]
    • 所有带属性的div
  • //div/a|//div/p
    • 所有div中的a和p元素
  • //span|//ul
    • 文档中的span和ul元素
  • article/div/p|//span
    • 所有属于article元素的div元素的p元素以及文档中的所有span元素

how

In Scrapy

使用response对象中自带的xpath解析器:response.xpath()

指定xpath后调用值,只需要增加text(),如:response.xpath('/div/text()')

浏览器直接复制出来的xpath为什么定位不到元素?

因为浏览器复制出来的元素是包含了js运行结果的,也就是说,如果js增加了一些node节点,那我们在复制的时候也复制了这部分的node节点。然而,
我们可以知道,通过response返回的结果中是不包含js运行结果的,所以说直接浏览器复制的xpath路径是有可能提取不到元素的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# -*- coding: utf-8 -*-
import scrapy
import re
class JobboleSpider(scrapy.Spider):
# 爬虫名称
name = 'jobbole'
# url爬取域名白名单
allowed_domains = ['blog.jobbole.com']
# 开启爬取url列表
start_urls = ['http://blog.jobbole.com/111742/']
def parse(self, response):
# 提取
title = response.xpath('//div[@class="entry-header"]/h1/text()')
# 解压缩转数组
titleArr = title.extract()
# 提取第一个为标题
ret_title = titleArr[0]
# 提取创建日志
ret_create_date = response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract()[0].strip().replace(
" ·", "")
# 提取点赞
ret_praise_nums = int(response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0])
# 收藏
strFavNums = response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0]
reHandle = re.match(".*(\d+).*", strFavNums)
if reHandle:
ret_fav_nums = reHandle.group(1)
# 评论
strCommentNums = response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
reHandle = re.match(".*(\d+).*", strCommentNums)
if reHandle:
ret_comment_nums = reHandle.group(1)
# 正文
ret_content = response.xpath("//div[@class='entry']/text()").extract()[0]
# 标签
tagList = response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract()
tagList = [element for element in tagList if not element.strip().endswith("评论")]
ret_tags = ",".join(tagList)
pass

看一下运行结果,分析出来的属性以ret_开头,如下:

xpath结果

上述在Scrapy的spider程序中调试每次都需要等待?

这里介绍一个不需要等待的调试方法scrapy shell

好吧,我们来调试一下,命令行输入:scrapy shell http://blog.jobbole.com/111742/

在交互式命令行中调试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import re
# 提取
title = response.xpath('//div[@class="entry-header"]/h1/text()')
# 解压缩转数组
titleArr = title.extract()
# 提取第一个为标题
strTitle = titleArr[0]
# 提取创建日志
strCreateDate = response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract()[0].strip().replace(" ·", "")
# 提取点赞
intPraiseNums = int(response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0])
# 收藏
strFavNums = response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0]
reHandle = re.match(".*(\d+).*", strFavNums)
if reHandle:
print(reHandle.group(1))
# 评论
strCommentNums = response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
reHandle = re.match(".*(\d+).*", strCommentNums)
if reHandle:
print(reHandle.group(1))
# 正文
content = response.xpath("//div[@class='entry']/text()").extract()[0]
# 标签
tagList = response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract()
tagList = [element for element in tagList if not element.strip().endswith("评论")]
tags = ",".join(tagList)

知识点:

  • extract
    • 提取selecter中的数据
  • strip
    • 去掉空白
  • replace
    • 替换字符
  • contains
    • 包含属性
  • 数组过滤
    • [element for element in tagList if not element.strip().endswith("评论")]
  • 数组转字符串
    • “,”.join(tagList)

scrapy-使用

初始化virtualenv环境

1
2
3
4
5
6
## init 环境容器,从而保障独立的依赖
mkvirtualenv --python=/usr/bin/python3 scrapy_python3
## 切换到环境
workon scrapy_python3
## 安装scrapy
pip install scrapy

创建工程

1
2
3
4
5
6
## 创建工程
scrapy startproject ArticleSpider
## 进入工程目录
cd ArticleSpider
## 创建爬虫
scrapy genspider jobbole blog.jobbole.com

分析目录结构

scrapy项目结构

pycharm 设定解析器

python解析器

spider代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding: utf-8 -*-
import scrapy
class JobboleSpider(scrapy.Spider):
# 爬虫名称
name = 'jobbole'
# url爬取域名白名单
allowed_domains = ['blog.jobbole.com']
# 开启爬取url列表
start_urls = ['http://blog.jobbole.com/']
def parse(self, response):
pass

JobboleSpider继承了scrapy.Spider类,在scrapy.Spider类中定义如下:

scrapy.spider

可以知道,scrapy把每一个start_urls中定义的url,创建成Request对象,通过yield关键词下发给scrapy下载器
从而实现高并发、高效率下载。

pycharm调试方法

pycharm执行、调试程序必须有一个入口方法,我们需要定义一个程序,发起scrapy crawl xxx命令来启动爬虫,具体做法如下:

  • 新增一个main.py在爬虫工程目录/ArticleSpider:
1
2
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'jobbole'])
  • 打一个断点

断点是为了更好的调试程序,反馈在这个语句中上下文的变量情况

调试断点

关闭robot协议

settings.pyROBOTSTXT_OBEY默认定义为True表示遵循robots协议,这样在协议中不允许访问的url我们就不能抓取了,
我们必须来关闭他。

很简单:ROBOTSTXT_OBEY = False

运行结果

断点调试结果

通过观察结果,我们可以知道,parse方法在下载器下载完毕页面后被调用,这里可以捕获到Response返回对象,
包含Response的全部信息,如:返回结果、返回状态码等等…

总结

我们花费了大量篇幅来介绍爬虫的基础知识、环境的准备等,今天终于开始爬虫的实践了,很激动…
在实战篇,将深入探讨爬虫的使用方法,原理以及高级设置等~~~