用 viewpager 创建欢迎页轮播图

使用 viewpager 创建欢迎页轮播图

布局文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<android.support.v4.view.ViewPager
android:id="@+id/view_pager_2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_weight="1"/>

<LinearLayout
android:id="@+id/view_dot_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal"/>

其中 view_dot_container 是轮播图下显示第几页的指示点的容器

adapter 需要继承自 PagerAdapter

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
public class HelloAdapter extends PagerAdapter {

private Context mContext;
private int[] imgs;
private List<ImageView> mImageViews;

public HelloAdapter(Context context, int[] imgs) {
mContext = context;
this.imgs = imgs;
mImageViews = new ArrayList<>();
for (int img : imgs) {
ImageView view = new ImageView(context);

ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
view.setLayoutParams(params);
Glide.with(context).load(img).into(view);
mImageViews.add(view);
}
}

@Override
public int getCount() {
return imgs.length;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mImageViews.get(position));
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(mImageViews.get(position));
return mImageViews.get(position);
}

@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}

在这里。我是传进来一组本地的 drawable 资源 ID,也可以传进来一个 View 的集合(如 List<ImageView> )。

PagerAdapter 的重点是 instantiateItem 和 isViewFromObject 方法。

instantiateItem 这个方法,return 一个对象,这个对象表明了 PagerAdapter 选择哪个对象放在当前的 ViewPager 中;

isViewFromObject 用来判断 instantiateItem(ViewGroup, int) 函数所返回来的 Key 与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个View)

destroyItem 在重写该方法的时候主动从 ViewPager 中移除该 ContentView 。

初始化

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
private List<ImageView> mDotViews;//用来存放指示点的集合

private void initViewPager() {
int[] imgs = new int[]{R.drawable.a, R.drawable.b, R.mipmap.c};
HelloAdapter helloAdapter = new HelloAdapter(this, imgs);
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
viewPager.setAdapter(helloAdapter);
viewPager.addOnPageChangeListener(this);//监听 ViewPager 的滚动事件
mDotViews = new ArrayList<>();

dotContainer = (LinearLayout) findViewById(R.id.view_dot_container);
for (int img : imgs) {
ImageView dotView = new ImageView(this);
dotView.setImageResource(R.drawable.bg_radius_4);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(30, 30);
params.setMargins(15, 0, 15, 0);
dotView.setLayoutParams(params);
mDotViews.add(dotView);
dotContainer.addView(dotView);
}
viewPager.setCurrentItem(0);
mDotViews.get(0).setImageResource(R.drawable.check_dot);
}

@Override
public void onPageScrolled(int position, float positionOffset, intpositionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {
for (int i = 0; i < mDotViews.size(); i++) {
tvTitle.setText(titles[position]);
if (i == position) {
mDotViews.get(i).setImageResource(R.drawable.check);
} else {
mDotViews.get(i).setImageResource(R.drawable.uncheck);
}
}
}

@Override
public void onPageScrollStateChanged(int state) {

}
  1. onPageSelected(int position):这个方法有一个参数 position ,代表哪个页面被选中。当用手指滑动翻页的时候,如果翻动成功了(滑动的距离够长),手指抬起来就会立即执行这个方法,position 就是当前滑动到的页面。如果直接 setCurrentItem 翻页,那 position 就和 setCurrentItem 的参数一致,这种情况在 onPageScrolled 执行方法前就会立即执行。

  2. onPageScrolled(int position,float positionOffset, int positionOffsetPixels):这个方法会在屏幕滚动过程中不断被调用。有三个参数,第一个position,这个参数要特别注意一下。当用手指滑动时,如果手指按在页面上不动,position和当前页面index是一致的;如果手指向左拖动(相应页面向右翻动),这时候position大部分时间和当前页面是一致的,只有翻页成功的情况下最后一次调用才会变为目标页面;如果手指向右拖动(相应页面向左翻动),这时候position大部分时间和目标页面是一致的,只有翻页不成功的情况下最后一次调用才会变为原页面。当直接设置setCurrentItem翻页时,如果是相邻的情况(比如现在是第二个页面,跳到第一或者第三个页面),如果页面向右翻动,大部分时间是和当前页面是一致的,只有最后才变成目标页面;如果向左翻动,position和目标页面是一致的。这和用手指拖动页面翻动是基本一致的。如果不是相邻的情况,比如我从第一个页面跳到第三个页面,position先是0,然后逐步变成1,然后逐步变成2;我从第三个页面跳到第一个页面,position先是1,然后逐步变成0,并没有出现为2的情况。positionOffset是当前页面滑动比例,如果页面向右翻动,这个值不断变大,最后在趋近1的情况后突变为0。如果页面向左翻动,这个值不断变小,最后变为0。positionOffsetPixels是当前页面滑动像素,变化情况和positionOffset一致。

  3. onPageScrollStateChanged(int state):这个方法在手指操作屏幕的时候发生变化。有三个值:0(END),1(PRESS) , 2(UP) 。当用手指滑动翻页时,手指按下去的时候会触发这个方法,state值为1,手指抬起时,如果发生了滑动(即使很小),这个值会变为2,然后最后变为0 。总共执行这个方法三次。一种特殊情况是手指按下去以后一点滑动也没有发生,这个时候只会调用这个方法两次,state值分别是1,0 。

四年之久

2017.9.16 周六
等了一年,终于等来了这天。我之前无数次想象着这天的场景。七年已经过去了四年。

提前一天,准备着要去的地方,最近的路线,路上的商家。你说去人少的地方,好,那就先不去故宫,你说不想去太远的地方,那颐和园798以后有时间再去。之前想过无数次在北京相见时的场景,没想到到了这天我却迟到了。

我记得跟你说过我特别讨厌北京,因为三张图给了我继续留在北京的支持,一张是雪中的故宫,我一直认为故宫真的是北京最值得逛的一处景点,无论去多少次都不会感到无聊,那种作为曾经皇城的尊严是别的地方感受不到的。第二张是角楼的照片,应该是每个摄影爱好者心中的圣地吧,我也去拍过,手机直拍,加上是正中午,糊的一片。第三张就是你,起码是一部分因为你让我选择继续留在北京。

之前有人问我有没有喜欢的人,我说不知道,因为我真的不知道自己还喜不喜欢你真的不知道。不过,昨天我终于确定了,我喜欢你,喜欢了你四年,真的,只是有时候可能不是特别喜欢那种。之前怕被拒绝,怕尴尬,但是现在都四年了,还怕什么,四年都过来了。这四年太年轻,什么都不懂,一直没有行动,还总是自己感动自己。当时说的七年现在已经过去一大半了。有时候经常会无缘无故的想起你,想到未来,想起与你的点点滴滴。我已经习惯了一个人的生活,不过这种生活的前提是未来有你,不然的话我不知道我要怎么过来,像我这么无聊的人。

我要加油啊!带你去看故宫的雪。

Scrapy 入门

scrapy 介绍

scrapy 是一个基于 Python 语言的爬虫框架。有了框架的存在,我们可以更方便的爬虫了,框架帮我们封装好了下载等模块,而且更重要的是框架使用了异步的模式,加快了爬虫的速度。

环境搭建

在命令行中执行 conda install Scrapy 这样就安装好了 Scrapy 模块,是不是特别简单。ahahahha

开始

接下来就要开始我们的 scrapy 之路了。

新建项目

使用 Scrapy 第一步:创建项目,命令行进入你需要放置项目的目录,然后执行

scrapy startproject ScrapyDemo #ScrapyDemo是项目名称

目录结构

这时会在目录下多出一个 ScrapyDemo 文件夹,这就是 Scrapy 项目了,项目的结构如下

1
2
3
4
5
6
7
8
9
10
11
.
|-- ScrapyDemo
| `-- ScrapyDemo
| |-- spiders
| | `-- __init__.py
| |-- __init__.py
| |-- items.py
| |-- middlewares.py
| |-- pipelines.py
| `-- settings.py
`-- scrapy.cfg

目录说明:

  • ScrapyDemo(外层) 项目总目录
  • ScrapyDemo(内层) 项目目录
  • scrapy.cfg 项目的配置文件
  • spiders 放置我们的爬虫代码的目录
  • items.py 定义我们需要获取的字段
  • middlewares.py
  • pipelines.py 用来定义存储
  • settings.py 项目设置文件

还有一点要注意的是,Scrapy 默认是不能在 IDE 中调试的,所以要在项目总目录下新建一个 entrypoint.py 文件,写下以下内容:

1
2
3
4
from scrapy.cmdline import execute

# 前两个参数是不变的,第三个参数是自己定义的 spider 的名字
execute(['scrapy', 'crawl', 'dingdian'])

现在整个目录应该是这样(盗图..)

架构

下面我们先来看一下框架的架构图

Scrapy Engine: 这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等等!(像不像人的身体?)

Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。

Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理,

Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),

Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)

Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件

Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spiders中间‘通信’的功能组件(比如进入Spiders的Responses;和从Spiders出去的Requests)

数据在整个Scrapy的流向:

程序运行的时候,

引擎:Hi!Spider, 你要处理哪一个网站?

Spiders:我要处理23wx.com

引擎:你把第一个需要的处理的URL给我吧。

Spiders:给你第一个URL是XXXXXXX.com

引擎:Hi!调度器,我这有request你帮我排序入队一下。

调度器:好的,正在处理你等一下。

引擎:Hi!调度器,把你处理好的request给我,

调度器:给你,这是我处理好的request

引擎:Hi!下载器,你按照下载中间件的设置帮我下载一下这个request

下载器:好的!给你,这是下载好的东西。(如果失败:不好意思,这个request下载失败,然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载。)

引擎:Hi!Spiders,这是下载好的东西,并且已经按照Spider中间件处理过了,你处理一下(注意!这儿responses默认是交给def parse这个函数处理的)

Spiders:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,这是我需要跟进的URL,将它的responses交给函数 def xxxx(self, responses)处理。还有这是我获取到的Item。

引擎:Hi !Item Pipeline 我这儿有个item你帮我处理一下!调度器!这是我需要的URL你帮我处理下。然后从第四步开始循环,直到获取到你需要的信息,

注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的 URL,Scrapy会重新下载。)

写代码

建立一个项目之后:

第一件事就是在 items.py 文件中定义我们需要的字段,用来临时存储要保存的数据,方便以后数据的持久化存储,比如 数据库 文本文件等。

第二件事是在 spiders.py 中编写自己的爬虫代码

第三件事是在 pipelines.py 中存储自己的数据

建议:在大家调试的时候在settings.py中取消下面几行的注释:

1
2
3
4
5
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache'
HTTPCACHE_IGNORE_HTTP_CODES = []
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

上面代码的作用是 Scrapy 会缓存你有的 Requests! 当你再次请求时,如果存在缓存文档则返回缓存文档,而不是去网站请求,这样既加快了本地调试速度,也减轻了 网站的压力。一举多得

定义字段

要根据自己要爬取的内容来定义字段。比如,在这里我们要爬的是小说站点就需要定义,小说名字,作者,小说地址,连载状态,字数,文章类别 等字段。

就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ScrapydemoItem(scrapy.Item):
# 小说名字
name = scrapy.Field()
# 作者
author = scrapy.Field()
# 小说地址
novelurl = scrapy.Field()
# 连载状态
serialstatus = scrapy.Field()
# 连载字数
serialnumber = scrapy.Field()
# 类别
category = scrapy.Field()
# 编号
novel_id = scrapy.Field()

编写 spider

在 spiders 目录下新建一个 ScrapyDemo.py 文件,并导入我们需用的模块

1
2
3
4
5
6
7
import scrapy 
import re
from bs4 import BeautifulSoup
from scrapy.http import Request
from ScrapyDemo.items import ScrapydemoItem

class Muspider(scrapy.Spider):

我们需要从一个地址入手开始爬取,我在顶点小说上没有发现有全站小说地址,但是我找到每个分类地址全部小说:

玄幻魔幻:http://www.x23us.com/class/1_1.html

武侠修真:http://www.x23us.com/class/2_1.html

都市言情:http://www.x23us.com/class/3_1.html

历史军事:http://www.x23us.com/class/4_1.html

侦探推理:http://www.x23us.com/class/5_1.html

网游动漫:http://www.x23us.com/class/6_1.html

科幻小说:http://www.x23us.com/class/7_1.html

恐怖灵异:http://www.x23us.com/class/8_1.html

散文诗词:http://www.x23us.com/class/9_1.html

其他:http://www.x23us.com/class/10_1.html

全本:http://www.x23us.com/quanben/1

好啦!入口地址我们找到了,现在开始写第一部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import scrapy
import re
from bs4 import BeautifulSoup
from scrapy.http import Request
from ScrapyDemo.items import ScrapydemoItem

class Myspider(scrapy.Spider):
name = 'ScrapyDemo'
allowed_domains = ['x23us.com']
bash_url = 'http://www.x23us.com/class/'
bashurl = '.html'

def start_requests(self):
for i in range(1, 11):
url = self.bash_url + str(i) + '_1' + self.bashurl
yield Request(url,self.parse)

def parse(self, response):
print(response.text)

首先我们创建一个类 Myspider;这个类继承自 scrapy.Spider

定义了一个 allowed_domains ;这个不是必须的;但是在某写情况下需要用得到,比如使用爬取规则的时候就需要了;它的作用是只会跟进存在于 allowed_domains 中的 URL。不存在的 URL 会被忽略。

第九行定义的 name 是之前我们在 entrypoint.py 文件中的第三个参数 此 name 在整个项目中有且只能有一个、名字不可重复!!!

之后可以运行 entrypoint.py 文件来检查一下代码时候可以正常工作。

请求就这么轻而易举的实现了啊!简直So Easy!

继续 继续!

我们需要历遍所有页面才能取得所有的小说页面连接:

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
import scrapy
import re
from bs4 import BeautifulSoup
from scrapy.http import Request
from ScrapyDemo.items import ScrapydemoItem


class Muspider(scrapy.Spider):
name = 'ScrapyDemo'
allowed_domains = ['x23us.com']
bash_url = 'http://www.x23us.com/class/'
bashurl = '.html'

def start_requests(self):
for i in range(1, 11):
url = self.bash_url + str(i) + '_1' + self.bashurl
yield Request(url, self.parse)

def parse(self, response):
max_span = BeautifulSoup(response.text, 'lxml').find('div', class_='pagelink').find_all('a')[-1].get_text()
bashurl = str(response.url)[:-6]
for num in range(1, int(max_span) + 1):
url = bashurl + str(num) + self.bashurl
yield Request(url, self.get_name)

def get_name(self, response):
novels = BeautifulSoup(str(response), 'lxml').find_all('tr', bgcolor='#F2F2F2')
for navel in novels:
a = navel.find('a')
name = a['title']
url = a['href']
yield Request(url, callback=self.get_chapterurl, meta={'name': name, 'url': url})

def get_chapterurl(self, response):
item = ScrapydemoItem()
item['name'] = str(response.meta['name']).replace('\xa0', '')
item['novelurl'] = response.meta['url']
category = BeautifulSoup(response.text, 'lxml').find('table').find('a').get_text()
author = BeautifulSoup(response.text, 'lxml').find('table').find_all('td')[1].get_text()
bash_url = BeautifulSoup(response.text, 'lxml').find('p', class_='btnlinks').find('a', class_='read')['href']
name_id = str(bash_url)[-6:-1].replace('/', '')
item['category'] = str(category).replace('/', '')
item['author'] = str(author).replace('/', '')
item['name_id'] = name_id
return item

自定义 Pipeline

做一个自定义的MySQL的Pipeline。首先为了能好区分框架自带的Pipeline,我们把MySQL的Pipeline单独放到一个目录里面。

pipelines.py 这个是我们写存放数据的文件

sql.py 看名字就知道,需要的sql语句。

首先是需要的 MySQL 表,

1
2
3
4
5
6
7
8
9
DROP TABLE IF EXISTS `dd_name`;
CREATE TABLE `dd_name` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`xs_name` varchar(255) DEFAULT NULL,
`xs_author` varchar(255) DEFAULT NULL,
`category` varchar(255) DEFAULT NULL,
`name_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb4;

记得在 settings.py 文件中定义好 MySQL 的配置文件

1
2
3
4
5
MYSQL_HOSTS = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PORT = '3306'
MYSQL_PASSWORD = '****'
MYSQL_DB = 'xiaoshuo'

在开始写sql.py之前,我们需要安装一个Python操作MySQL的包,来自MySQL官方的一个包:点我下载

下载完成后解压出来,从 cmd 进入该目录的绝对路径,然后 python setup.py install ;即可完成安装(记得以管理员身份打开命令行)

sql.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
import mysql.connector

from ScrapyDemo import settings

MYSQL_USER = settings.MYSQL_USER
MYSQL_PASSWORD = settings.MYSQL_PASSWORD
MYSQL_HOST = settings.MYSQL_HOSTS
MYSQL_PORT = settings.MYSQL_PORT
MYSQL_DB = settings.MYSQL_DB

cnx = mysql.connector.connect(user=MYSQL_USER, password=MYSQL_PASSWORD, host=MYSQL_HOST, database=MYSQL_DB)
cur = cnx.cursor(buffered=True)


class Sql:
@classmethod
def insert_dd_name(cls, xs_name, xs_author, category, name_id):
sql = 'INSERT INTO dd_name (`xs_name`,`xs_author`,`category`,``name_id) VALUES (%(xs_name)s,%(xs_author)s,,%(category)s,,%(name_id)s)'
VALUE = {
'xs_name': xs_name,
'xs_author': xs_author,
'category': category,
'name_id': name_id
}
cur.execute(sql, VALUE)
cnx.commit()

@classmethod
def select_name(cls, name_id):
sql = 'SELECT EXISTS(SELECT 1 FROM dd_name WHERE name_id=%(name_id)s)'
value = {
'name_id': name_id
}
cur.execute(sql, value)
cnx.commit()

来开始写pipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from ScrapyDemo.items import ScrapydemoItem
from .sql import Sql


class ScrapyPipeline(object):
def process_item(self, item, sqider):
if isinstance(item, ScrapydemoItem):
name_id = item['name_id']
ret = Sql.select_name(name_id)
if ret[0] == 1:
print('已经存在了')
pass
else:
xs_name = item['xs_name']
xs_author = item['xs_author']
category = item['category']
Sql.insert_dd_name(xs_name, xs_author, category, name_id)
print('开始存小说标题')

定义了一个process_item函数并有,item和spider这两个参数(请注意啊!这两玩意儿 务必!!!要加上!!千万不能少!!!!务必!!!务必!!!

搞完!下面我们启用这个Pipeline在settings中作如下设置:

后面的 1 是优先级程度(1-1000随意设置,数值越低,组件的优先级越高)

下面我们开始还剩下的一些内容获取:小说章节 和章节内容

首先我们在 item.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
import scrapy

class ScrapydemoItem(scrapy.Item):
# 小说名字
name = scrapy.Field()
# 作者
author = scrapy.Field()
# 小说地址
novelurl = scrapy.Field()
# 连载状态
serialstatus = scrapy.Field()
# 连载字数
serialnumber = scrapy.Field()
# 类别
category = scrapy.Field()
# 编号
novel_id = scrapy.Field()


class Content(scrapy.Item):
id_name = scrapy.Field()
chaptercontent = scrapy.Field()
num = scrapy.Field()
chapter_url = scrapy.Field()
chapter_name = scrapy.Field()

继续编写Spider文件:

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
def get_chapterurl(self, response):
item = ScrapydemoItem()
item['name'] = str(response.meta['name']).replace('\xa0', '')
item['novelurl'] = response.meta['url']
category = BeautifulSoup(response.text, 'lxml').find('table').find('a').get_text()
author = BeautifulSoup(response.text, 'lxml').find('table').find_all('td')[1].get_text()
bash_url = BeautifulSoup(response.text, 'lxml').find('p', class_='btnlinks').find('a', class_='read')['href']
name_id = str(bash_url)[-6:-1].replace('/', '')
item['category'] = str(category).replace('/', '')
item['author'] = str(author).replace('/', '')
item['name_id'] = name_id
yield item
yield Request(bash_url, callback=self.get_chapter, meta={'name_id': name_id})

def get_chapter(self, response):
urls = re.findall(r'<td class="L"><a href="(.*?)">(.*?)</a></td>', response.text)
num = 0
for url in urls:
num = num + 1
chapterurl = response.url + url[0]
chaptername = url[1]
yield Request(chapterurl, callback=self.get_chaptercontent, meta={
'num': num,
'name_id': response.meta['name_id'],
'chapter_url': chapterurl,
'chaptername': chaptername
})

def get_chaptercontent(self, response):
item = Content()
item['id_name'] = response.meta['name_id']
item['num'] = response.meta['num']
item['chapter_url'] = response.meta['chapter_url']
item['chapter_name'] = response.meta['chaptername']
content = BeautifulSoup(response.text, 'lxml').find('dd', id='contents').get_text()
item['content'] = str(content).replace('\xa0', '')
return item

注意 13\14 行,这个地方返回item是不能用return的哦!用了就结束了,程序就不会继续下去了,得用 yield

下面我们来写存储这部分spider的Pipeline:

数据表:

1
2
3
4
5
6
7
8
9
10
11
DROP TABLE IF EXISTS `dd_chaptername`;
CREATE TABLE `dd_chaptername` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`xs_chaptername` varchar(255) DEFAULT NULL,
`xs_content` text,
`id_name` int(11) DEFAULT NULL,
`num_id` int(11) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2726 DEFAULT CHARSET=gb18030;
SET FOREIGN_KEY_CHECKS=1;


下面是Pipeline:

有小伙伴注意,这儿比上面一个Pipeline少一个判断,因为我把判断移动到Spider中去了,这样就可以减少一次Request,减轻服务器压力。

改变后的Spider长这样:

妹子图爬虫第四弹

多线程多进程爬虫

之前我们的程序都可以用,但是速度太慢了,因为我们只用了一个线程一个进程来进行爬虫,大部分时间都在等待,效率太低。所以接下来我们要加快我们的爬虫效率。

同上篇,我们用数据库的方式来解决线程以及进程间的通信问题。

要爬取的 url 总共有三种状态:

  • OUTSTANDING(初始状态)
  • PROCESSING(正在下载状态)
  • COMPLETE(下载完成状态)

当一个所有初始的URL状态都为outstanding;当开始爬取的时候状态改为:processing;爬取完成状态改为:complete;失败的URL重置状态为:outstanding。为了能够处理URL进程被终止的情况、我们设置一个计时参数,当超过这个值时;我们则将状态重置为outstanding。下面是代码实现:

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
from datetime import datetime, timedelta
from pymongo import MongoClient, errors

class MogoQueue():

OUTSTANDING = 1 ##初始状态
PROCESSING = 2 ##正在下载状态
COMPLETE = 3 ##下载完成状态

def __init__(self, db, collection, timeout=300):##初始mongodb连接
self.client = MongoClient()
self.Client = self.client[db]
self.db = self.Client[collection]
self.timeout = timeout

def __bool__(self):
"""
这个函数,我的理解是如果下面的表达为真,则整个类为真
至于有什么用,后面我会注明的(如果我的理解有误,请指点出来谢谢,我也是Python新手)
$ne的意思是不匹配
"""
record = self.db.find_one(
{'status': {'$ne': self.COMPLETE}}
)
return True if record else False

def push(self, url, title): ##这个函数用来添加新的URL进队列
try:
self.db.insert({'_id': url, 'status': self.OUTSTANDING, '主题': title})
print(url, '插入队列成功')
except errors.DuplicateKeyError as e: ##报错则代表已经存在于队列之中了
print(url, '已经存在于队列中了')
pass
def push_imgurl(self, title, url):
try:
self.db.insert({'_id': title, 'statue': self.OUTSTANDING, 'url': url})
print('图片地址插入成功')
except errors.DuplicateKeyError as e:
print('地址已经存在了')
pass

def pop(self):
"""
这个函数会查询队列中的所有状态为OUTSTANDING的值,
更改状态,(query后面是查询)(update后面是更新)
并返回_id(就是我们的URL),MongDB好使吧,^_^
如果没有OUTSTANDING的值则调用repair()函数重置所有超时的状态为OUTSTANDING,
$set是设置的意思,和MySQL的set语法一个意思
"""
record = self.db.find_and_modify(
query={'status': self.OUTSTANDING},
update={'$set': {'status': self.PROCESSING, 'timestamp': datetime.now()}}
)
if record:
return record['_id']
else:
self.repair()
raise KeyError

def pop_title(self, url):
record = self.db.find_one({'_id': url})
return record['主题']

def peek(self):
"""这个函数是取出状态为 OUTSTANDING的文档并返回_id(URL)"""
record = self.db.find_one({'status': self.OUTSTANDING})
if record:
return record['_id']

def complete(self, url):
"""这个函数是更新已完成的URL完成"""
self.db.update({'_id': url}, {'$set': {'status': self.COMPLETE}})

def repair(self):
"""这个函数是重置状态$lt是比较"""
record = self.db.find_and_modify(
query={
'timestamp': {'$lt': datetime.now() - timedelta(seconds=self.timeout)},
'status': {'$ne': self.COMPLETE}
},
update={'$set': {'status': self.OUTSTANDING}}
)
if record:
print('重置URL状态', record['_id'])

def clear(self):
"""这个函数只有第一次才调用、后续不要调用、因为这是删库啊!"""
self.db.drop()

接下来就需要来爬取所有的链接地址来存进我们的数据库里了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Download import request
from mongodb_queue import MogoQueue
from bs4 import BeautifulSoup


spider_queue = MogoQueue('meinvxiezhenji', 'crawl_queue')
def start(url):
response = request.get(url, 3)
Soup = BeautifulSoup(response.text, 'lxml')
all_a = Soup.find('div', class_='all').find_all('a')
for a in all_a:
title = a.get_text()
url = a['href']
spider_queue.push(url, title)
"""上面这个调用就是把URL写入MongoDB的队列了"""

if __name__ == "__main__":
start('http://www.mzitu.com/all')

"""这一段儿就不解释了哦!超级简单的"""
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
import os
import time
import threading
import multiprocessing
from mongodb_queue import MogoQueue
from Download import request
from bs4 import BeautifulSoup

SLEEP_TIME = 1

def mzitu_crawler(max_threads=10):
crawl_queue = MogoQueue('meinvxiezhenji', 'crawl_queue') ##这个是我们获取URL的队列
##img_queue = MogoQueue('meinvxiezhenji', 'img_queue')
def pageurl_crawler():
while True:
try:
url = crawl_queue.pop()
print(url)
except KeyError:
print('队列没有数据')
break
else:
img_urls = []
req = request.get(url, 3).text
title = crawl_queue.pop_title(url)
mkdir(title)
os.chdir('D:\mzitu\\' + title)
max_span = BeautifulSoup(req, 'lxml').find('div', class_='pagenavi').find_all('span')[-2].get_text()
for page in range(1, int(max_span) + 1):
page_url = url + '/' + str(page)
img_url = BeautifulSoup(request.get(page_url, 3).text, 'lxml').find('div', class_='main-image').find('img')['src']
img_urls.append(img_url)
save(img_url)
crawl_queue.complete(url) ##设置为完成状态
##img_queue.push_imgurl(title, img_urls)
##print('插入数据库成功')

def save(img_url):
name = img_url[-9:-4]
print(u'开始保存:', img_url)
img = request.get(img_url, 3)
f = open(name + '.jpg', 'ab')
f.write(img.content)
f.close()

def mkdir(path):
path = path.strip()
isExists = os.path.exists(os.path.join("D:\mzitu", path))
if not isExists:
print(u'建了一个名字叫做', path, u'的文件夹!')
os.makedirs(os.path.join("D:\mzitu", path))
return True
else:
print(u'名字叫做', path, u'的文件夹已经存在了!')
return False

threads = []
while threads or crawl_queue:
"""
这儿crawl_queue用上了,就是我们__bool__函数的作用,为真则代表我们MongoDB队列里面还有数据
threads 或者 crawl_queue为真都代表我们还没下载完成,程序就会继续执行
"""
for thread in threads:
if not thread.is_alive(): ##is_alive是判断是否为空,不是空则在队列中删掉
threads.remove(thread)
while len(threads) < max_threads or crawl_queue.peek(): ##线程池中的线程少于max_threads 或者 crawl_qeue时
thread = threading.Thread(target=pageurl_crawler) ##创建线程
thread.setDaemon(True) ##设置守护线程
thread.start() ##启动线程
threads.append(thread) ##添加进线程队列
time.sleep(SLEEP_TIME)

def process_crawler():
process = []
num_cpus = multiprocessing.cpu_count()
print('将会启动进程数为:', num_cpus)
for i in range(num_cpus):
p = multiprocessing.Process(target=mzitu_crawler) ##创建进程
p.start() ##启动进程
process.append(p) ##添加进进程队列
for p in process:
p.join() ##等待进程队列里面的进程结束

if __name__ == "__main__":
process_crawler()

妹子图爬虫第三弹

前面两篇的教程教大家写了一个基础的爬虫程序,但是有问题啊,每次开始时都要重新下载,很难受。所以我们要解决这个问题,关键点在于要把我们爬过的页面记录下来,避免重复。在这里,原文作者使用 MongoDB (一个基于分布式文件存储的非关系型数据库) 来存储数据的,我也是不怎么明白什么是 非关系型数据库…不过这里有教程,大家可以看看。

首先是 MongoDB 的安装。例如把它安在 C 盘下 D:\software\MongoDB\Server ,之后需要创建两个目录:

D:\software\MongoDB\mongod.log(文件) 存储日志

D:\software\MongoDB\db 存储数据

然后以管理员身份打开命令行窗口,执行以下命令

"D:\software\MongoDB\Server\3.4\bin\mongod.exe" --config "D:\software\MongoDB\Server\3.4\mongod.cfg" --install

如图安装成功

用命令 net start MongoDB 来启动服务。

对了,还需要安装 MongoDB 的 python 模块 pip install PyMongo

现在我们在上一篇博文完成的代码中导入模块:

from pymongo import MongoClient

代码改造第一步,在类 mzitu 里添加一个函数:

1
2
3
4
5
6
7
def __init__(self):
client = MongoClient() ##与MongDB建立连接(这是默认连接本地MongDB数据库)
db = client['meinvxiezhenji'] ## 选择一个数据库
self.meizitu_collection = db['meizitu'] ##在meizixiezhenji这个数据库中,选择一个集合
self.title = '' ##用来保存页面主题
self.url = '' ##用来保存页面地址
self.img_urls = [] ##初始化一个 列表 用来保存图片地址

之后要改一下 all_url 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def all_url(self, url):
html = down.get(url, 3)
all_a = BeautifulSoup(html.text, 'lxml').find('div', class_='all').find_all('a')
for a in all_a:
title = a.get_text()
self.title = title ##将主题保存到self.title中
print(u'开始保存:', title)
path = str(title).replace("?", '_')
self.mkdir(path)
os.chdir("D:\mzitu\\"+path)
href = a['href']
self.url = href ##将页面地址保存到self.url中
if self.meizitu_collection.find_one({'主题页面': href}): ##判断这个主题是否已经在数据库中、不在就运行else下的内容,在则忽略。
print(u'这个页面已经爬取过了')
else:
self.html(href)

接着改 html 函数:

1
2
3
4
5
6
7
8
def html(self, href):
html = down.get(href, 3)
max_span = BeautifulSoup(html.text, 'lxml').find_all('span')[10].get_text()
page_num = 0 ##这个当作计数器用 (用来判断图片是否下载完毕)
for page in range(1, int(max_span) + 1):
page_num = page_num + 1 ##每for循环一次就+1 (当page_num等于max_span的时候,就证明我们的在下载最后一张图片了)
page_url = href + '/' + str(page)
self.img(page_url, max_span, page_num) ##把上面我们我们需要的两个变量,传递给下一个函数。

改 img 函数…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def img(self, page_url, max_span, page_num): ##添加上面传递的参数
img_html = down.get(page_url, 3)
img_url = BeautifulSoup(img_html.text, 'lxml').find('div', class_='main-image').find('img')['src']
self.img_urls.append(img_url) ##每一次 for page in range(1, int(max_span) + 1)获取到的图片地址都会添加到 img_urls这个初始化的列表
if int(max_span) == page_num: ##我们传递下来的两个参数用上了 当max_span和Page_num相等时,就是最后一张图片了,最后一次下载图片并保存到数据库中。
self.save(img_url)
post = { ##这是构造一个字典,里面有啥都是中文,很好理解吧!
'标题': self.title,
'主题页面': self.url,
'图片地址': self.img_urls,
'获取时间': datetime.datetime.now()
}
self.meizitu_collection.save(post) ##将post中的内容写入数据库。
print(u'插入数据库成功')
else: ##max_span 不等于 page_num执行这下面
self.save(img_url)

完整的代码在此

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
from bs4 import BeautifulSoup
import os
from Download import down ##导入模块变了一下
from pymongo import MongoClient
import datetime

class mzitu():

def __init__(self):
client = MongoClient() ##与MongDB建立连接(这是默认连接本地MongDB数据库)
db = client['meinvxiezhenji'] ## 选择一个数据库
self.meizitu_collection = db['meizitu'] ##在meizixiezhenji这个数据库中,选择一个集合
self.title = '' ##用来保存页面主题
self.url = '' ##用来保存页面地址
self.img_urls = [] ##初始化一个 列表 用来保存图片地址

def all_url(self, url):
html = down.get(url, 3)
all_a = BeautifulSoup(html.text, 'lxml').find('div', class_='all').find_all('a')
for a in all_a:
title = a.get_text()
self.title = title ##将主题保存到self.title中
print(u'开始保存:', title)
path = str(title).replace("?", '_')
self.mkdir(path)
os.chdir("D:\mzitu\\"+path)
href = a['href']
self.url = href ##将页面地址保存到self.url中
if self.meizitu_collection.find_one({'主题页面': href}): ##判断这个主题是否已经在数据库中、不在就运行else下的内容,在则忽略。
print(u'这个页面已经爬取过了')
else:
self.html(href)

def html(self, href):
html = down.get(href, 3)
max_span = BeautifulSoup(html.text, 'lxml').find_all('span')[10].get_text()
page_num = 0 ##这个当作计数器用 (用来判断图片是否下载完毕)
for page in range(1, int(max_span) + 1):
page_num = page_num + 1 ##每for循环一次就+1 (当page_num等于max_span的时候,就证明我们的在下载最后一张图片了)
page_url = href + '/' + str(page)
self.img(page_url, max_span, page_num) ##把上面我们我们需要的两个变量,传递给下一个函数。

def img(self, page_url, max_span, page_num): ##添加上面传递的参数
img_html = down.get(page_url, 3)
img_url = BeautifulSoup(img_html.text, 'lxml').find('div', class_='main-image').find('img')['src']
self.img_urls.append(img_url) ##每一次 for page in range(1, int(max_span) + 1)获取到的图片地址都会添加到 img_urls这个初始化的列表
if int(max_span) == page_num: ##我们传递下来的两个参数用上了 当max_span和Page_num相等时,就是最后一张图片了,最后一次下载图片并保存到数据库中。
self.save(img_url)
post = { ##这是构造一个字典,里面有啥都是中文,很好理解吧!
'标题': self.title,
'主题页面': self.url,
'图片地址': self.img_urls,
'获取时间': datetime.datetime.now()
}
self.meizitu_collection.save(post) ##将post中的内容写入数据库。
print(u'插入数据库成功')
else: ##max_span 不等于 page_num执行这下面
self.save(img_url)


def save(self, img_url):
name = img_url[-9:-4]
print(u'开始保存:', img_url)
img = down.get(img_url, 3)
f = open(name + '.jpg', 'ab')
f.write(img.content)
f.close()

def mkdir(self, path):
path = path.strip()
isExists = os.path.exists(os.path.join("D:\mzitu", path))
if not isExists:
print(u'建了一个名字叫做', path, u'的文件夹!')
os.makedirs(os.path.join("D:\mzitu", path))
return True
else:
print(u'名字叫做', path, u'的文件夹已经存在了!')
return False


Mzitu = mzitu() ##实例化
Mzitu.all_url('http://www.mzitu.com/all') ##给函数all_url传入参数 你可以当作启动爬虫(就是入口)

妹子图爬虫第二弹

上一篇文章中教大家去爬取妹子图,但是会出现很多问题,比如爬着爬着发现一直在报错,重新再来的话又得从第一张图开始下,太麻烦。通常是因为网站的反爬虫策略起了作用。

一般反爬虫策略有以下几种:

  • 后台对访问进行统计,如果单个IP访问超过阈值,予以封锁。
  • 后台对访问进行统计,如果单个session访问超过阈值,予以封锁。
  • 后台对访问进行统计,如果单个userAgent访问超过阈值,予以封锁。
  • 以上的组合。

针对上面的第一二条,来写个基本的下载模块。原理就是利用的不同的 User-Ahent 和 IP 来进行爬虫。

首先随便就可以在网上找到很多 User-Agent 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"

新建个下载的类然后改下代码

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
import requests
import re
import random

class download(object):
def __init__(self):
self.user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]

def get(self, url):
UA = random.choice(self.user_agent_list) ##从self.user_agent_list中随机取出一个字符串(聪明的小哥儿一定发现了这是完整的User-Agent中:后面的一半段)
headers = {'User-Agent': UA} ##构造成一个完整的User-Agent (UA代表的是上面随机取出来的字符串哦)
response = requests.get(url, headers=headers) ##这样服务器就会以为我们是真的浏览器了
return response

下一步就是要找一些能用的 IP 了。网上有一些 IP 代理的网站,通常会发一些免费的代理 IP,比如 http://haoip.cc/tiqu.htm

下面的代码可以爬下来这些 IP。

1
2
3
4
5
6
7
8
iplist = [] ##初始化一个list用来存放我们获取到的IP
html = requests.get("http://haoip.cc/tiqu.htm")##不解释咯
iplistn = re.findall(r'r/>(.*?)<b', html.text, re.S) ##表示从html.text中获取所有r/><b中的内容,re.S的意思是包括匹配包括换行符,findall返回的是个list哦!
for ip in iplistn:
i = re.sub('\n', '', ip)##re.sub 是re模块替换的方法,这儿表示将\n替换为空
iplist.append(i.strip()) ##添加到我们上面初始化的list里面, i.strip()的意思是去掉字符串的空格哦!!
print(i.strip())
print(iplist)

然后一顿操作

Download.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
import requests
import re
import random
import time


class download():

def __init__(self):

self.iplist = [] ##初始化一个list用来存放我们获取到的IP
html = requests.get("http://haoip.cc/tiqu.htm") ##不解释咯
iplistn = re.findall(r'r/>(.*?)<b', html.text, re.S) ##表示从html.text中获取所有r/><b中的内容,re.S的意思是包括匹配包括换行符,findall返回的是个list哦!
for ip in iplistn:
i = re.sub('\n', '', ip) ##re.sub 是re模块替换的方法,这儿表示将\n替换为空
self.iplist.append(i.strip()) ##添加到我们上面初始化的list里面

self.user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]

def get(self, url, timeout, proxy=None, num_retries=6): ##给函数一个默认参数proxy为空
UA = random.choice(self.user_agent_list) ##从self.user_agent_list中随机取出一个字符串
headers = {'User-Agent': UA} ##构造成一个完整的User-Agent (UA代表的是上面随机取出来的字符串哦)

if proxy == None: ##当代理为空时,不使用代理获取response(别忘了response啥哦!之前说过了!!)
try:
return requests.get(url, headers=headers, timeout=timeout)##这样服务器就会以为我们是真的浏览器了
except:##如过上面的代码执行报错则执行下面的代码

if num_retries > 0: ##num_retries是我们限定的重试次数
time.sleep(10) ##延迟十秒
print(u'获取网页出错,10S后将获取倒数第:', num_retries, u'次')
return self.get(url, timeout, num_retries-1) ##调用自身 并将次数减1
else:
print(u'开始使用代理')
time.sleep(10)
IP = ''.join(str(random.choice(self.iplist)).strip()) ##下面有解释哦
proxy = {'http': IP}
return self.get(url, timeout, proxy,) ##代理不为空的时候

else: ##当代理不为空
try:
IP = ''.join(str(random.choice(self.iplist)).strip()) ##将从self.iplist中获取的字符串处理成我们需要的格式(处理了些什么自己看哦,这是基础呢)
proxy = {'http': IP} ##构造成一个代理
return requests.get(url, headers=headers, proxies=proxy, timeout=timeout) ##使用代理获取response
except:

if num_retries > 0:
time.sleep(10)
IP = ''.join(str(random.choice(self.iplist)).strip())
proxy = {'http': IP}
print(u'正在更换代理,10S后将重新获取倒数第', num_retries, u'次')
print(u'当前代理是:', proxy)
return self.get(url, timeout, proxy, num_retries - 1)
else:
print(u'代理也不好使了!取消代理')
return self.get(url, 3)

request = download()

再改下 mzitu.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
from bs4 import BeautifulSoup
import os
from Download import request ##导入模块变了一下
from pymongo import MongoClient

class mzitu():


def all_url(self, url):

html = request.get(url, 3)
all_a = BeautifulSoup(html.text, 'lxml').find('div', class_='all').find_all('a')
for a in all_a:
title = a.get_text()
print(u'开始保存:', title)
path = str(title).replace("?", '_')
self.mkdir(path)
os.chdir("D:\mzitu\\"+path)
href = a['href']
self.html(href)

def html(self, href):
html = request.get(href, 3)
max_span = BeautifulSoup(html.text, 'lxml').find_all('span')[10].get_text()
for page in range(1, int(max_span) + 1):
page_url = href + '/' + str(page)
self.img(page_url)

def img(self, page_url):
img_html = request.get(page_url, 3)
img_url = BeautifulSoup(img_html.text, 'lxml').find('div', class_='main-image').find('img')['src']
self.save(img_url)

def save(self, img_url):
name = img_url[-9:-4]
print(u'开始保存:', img_url)
img = request.get(img_url, 3)
f = open(name + '.jpg', 'ab')
f.write(img.content)
f.close()

def mkdir(self, path):
path = path.strip()
isExists = os.path.exists(os.path.join("D:\mzitu", path))
if not isExists:
print(u'建了一个名字叫做', path, u'的文件夹!')
os.makedirs(os.path.join("D:\mzitu", path))
return True
else:
print(u'名字叫做', path, u'的文件夹已经存在了!')
return False




Mzitu = mzitu() ##实例化
Mzitu.all_url('http://www.mzitu.com/all') ##给函数all_url传入参数 你可以当作启动爬虫(就是入口)

注意:两个 py 文件要放在一个文件夹下,文件夹中还要新建一个叫 init.py 的空文件

妹子图爬虫第一弹

本教程是根据 小白爬虫第一弹之抓取妹子图 编写

首先上边卧槽哥写的这个爬虫教程看过好几遍了,自己也跟着写了两三次了。开始觉得 哈,这么厉害,python 爬虫原来这么简单,因垂丝汀。但是后来发现“自己跟着写过”这个过程,其实能记住的知识很少。加上最近自己一直很迷茫,不知道该往哪个方面发展,不知道该学什么,在 python 和 java 中纠结,在 爬虫 还是 服务器 中纠结。前两周还看了两周的 用Python和Pygame写游戏,慢慢学着发现,教程是基于 python2 的,而我的电脑安装的环境是 python3.5 的,很多教程上需要的包 和一些方法都不能用,而且教程已经是六年前的了,不能说已经被放弃了吧,还是有些过时,python 执行效率又不高,只能写一些小游戏。所以说还是从基础开始,往深里学,用心学习。

想起了之前看到的一张图

自己听过的不如读过的,读过的不如讨论过的,讨论过的不如教给其他人的。所以我决定以后要把自己学过的知识,自己写出来,写在博客里。这样自己也印象比较深刻,要是我写的博文能帮到其他人那就更好了。

基础环境

首先,我承认,我看上面所说的那个教程的时候,完全是被要爬取的网站吸引了。毕竟xx是学习第一动力(/滑稽)。

下面先来看看完成这篇教程所需要的基础环境

  • Python。本教程是基于 python3 来写。windows用户可以安装 anaconda,这是一个Python的科学计算发行版本,作者打包好多好多的包。
  • Requests。网络请求包。
  • beautifulsoup。可以从 html 文件中提取数据,非常方便,不用再搞那些恶心的正则表达式了。
  • LXML。一个HTML解析包 用于辅助beautifulsoup解析网页。

安装这些模块的话可以通过命令行来安装

1
2
3
4
5
6
7
conda install requests
conda install beautifulsoup4
conda install lxml
或者
pip install requests
pip install beautifulsoup4
pip install lxml

代码

重点来了!!我们要爬的网站是 妹子图 , 开不开心,激不激动。

网站的 http://www.mzitu.com/all 页面有整个网站全部的数据,贼良心。我们就在这个页面开启我们的爬虫之旅。Just do it!

1
2
3
4
5
6
7
8
9
10
11
import requests
import os
from bs4 import BeautifulSoup

headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"} ## 浏览器请求头

all_url = 'http://www.mzitu.com/all'

start_html = requests.get(all_url, headers=headers)##使用 requests 来获取 all_url 的内容,并且把 headers 作为请求头

print(start_html.text) ##把爬取到的内容打印出来 !!!记住网页内容是 .text

执行上面的代码会在控制台输出 all_url 页面的 html 源码。

目前只是爬到了网页的 html 源文件,但是我们是要爬图的啊!!!我们要美女图啊!!!爬虫才刚开始,慢慢来。

在 chrome 中打开 http://www.mzitu.com/all,按下 F12 调出开发者调试工具。

如图:

点击调试窗口左上角的那个小箭头,这样浏览器会根据你鼠标选中的视图显示相应的 html 代码。

如图,所有的图片链接地址都在 <li>...</li>

随便点开一个 <li> 标签

会发现图片页面的地址在<a>标签的href属性中、主题在<a>标签中。所以我们只要爬取所有<li>标签里的内容就能爬取我们想要的地址了。

但是页面里还有好多不符合要求的<li>标签,所以要筛选一下。通过观察发现,所有的文章主题地址都在 <div class='all'>...</div> 标签里。就是 <div class='all'> 里的 <a>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
from bs4 import BeautifulSoup

headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"} ## 浏览器请求头

all_url = 'http://www.mzitu.com/all'

start_html = requests.get(all_url, headers=headers)##使用 requests 来获取 all_url 的内容,并且把 headers 作为请求头

all_soup = BeutifulSoup(start_html.text,'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)

all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class为 all 的div标签,然后查找所有的<a>标签。
for a in all_a:
print(a)

运行结果如下:

然后就该提取我们想要的内容了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
from bs4 import BeautifulSoup

headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"} ## 浏览器请求头

all_url = 'http://www.mzitu.com/all'

start_html = requests.get(all_url, headers=headers)##使用 requests 来获取 all_url 的内容,并且把 headers 作为请求头

all_soup = BeutifulSoup(start_html.text,'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)

all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class为 all 的div标签,然后查找所有的<a>标签。
for a in all_a:
title = a.get_text()
href = a['href]

结果:

然后我们在随便打开一个上面打印出来的链接地址,会发现页面中只有一张图啊,点击 下一页 会发现 url 一直在变,就是在第一张图的 url 后跟 ‘/‘ + 数字,就代表第几张图。所以只需拿到最后一张图的页码,然后访问第一张图到最后一张图所有的url就可以爬到全部的图了。

观察发现页码栏都在<div class='pagenavi'> 标签下,而最后一页的页码是 标签中倒数第二个 <span> 中的内容,所以这一波操作来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
from bs4 import BeautifulSoup

headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"} ## 浏览器请求头

all_url = 'http://www.mzitu.com/all'

start_html = requests.get(all_url, headers=headers)##使用 requests 来获取 all_url 的内容,并且把 headers 作为请求头

all_soup = BeutifulSoup(start_html.text,'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)

all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class 为 all 的div标签,然后查找所有的<a>标签。
for a in all_a:
title = a.get_text()
href = a['href]
html = requests.get(href,headers=headers)
html_soup = BeautifulSoup(html.text,'lxml)
max_span = html_soup.find('div',class_='pagenavi').find_all('span')[-2].get_text()##[-2]代表倒数第二个
for page in range(1, int(max_span)+1):
page_url = href + '/' + str(page)
print(page_url)

这时我们就得到了每张图所在的链接地址,距离爬到我们心心念念的图片地址还有最后一步。发现我们需要的地址在 <div class=”main-image”> 中的 <img> 标签的 src 属性中。

继续操作一波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
from bs4 import BeautifulSoup

headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"} ## 浏览器请求头

all_url = 'http://www.mzitu.com/all'

start_html = requests.get(all_url, headers=headers)##使用 requests 来获取 all_url 的内容,并且把 headers 作为请求头

all_soup = BeutifulSoup(start_html.text,'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)

all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class 为 all 的div标签,然后查找所有的<a>标签。
for a in all_a:
title = a.get_text()
href = a['href]
html = requests.get(href,headers=headers)
html_soup = BeautifulSoup(html.text,'lxml)
max_span = html_soup.find('div',class_='pagenavi').find_all('span')[-2].get_text()##[-2]代表倒数第二个
for page in range(1, int(max_span)+1):
page_url = href + '/' + str(page)
page_html = requests.get(page_url,headers=headers)
page_soup = BeautifulSoup(page_html.text, 'lxml')
img_url = page_soup.find('div', class_='main-image').find('img')['src']
print(img_url)

hahahah,完美,这不就是我们想要的嘛。但是还要继续操作,我们只拿到了图片的 url ,还要把这些图片下载到本地才算“爬虫”啊。

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
import requests ##导入requests
from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
import os


headers = {'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}##浏览器请求头(大部分网站没有这个请求头会报错、请务必加上哦)
all_url = 'http://www.mzitu.com/all' ##开始的URL地址
start_html = requests.get(all_url, headers=headers) ##使用requests中的get方法来获取all_url(就是:http://www.mzitu.com/all这个地址)的内容 headers为上面设置的请求头、请务必参考requests官方文档解释
Soup = BeautifulSoup(start_html.text, 'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)
all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class为 all 的div标签,然后查找所有的<a>标签。
for a in all_a:
title = a.get_text() #取出a标签的文本
path = str(title).strip() ##去掉空格
os.makedirs(os.path.join("D:\mzitu", path)) ##创建一个存放套图的文件夹
os.chdir("D:\mzitu\\"+path) ##切换到上面创建的文件夹
href = a['href'] #取出a标签的href 属性
html = requests.get(href, headers=headers) ##上面说过了
html_Soup = BeautifulSoup(html.text, 'lxml') ##上面说过了
max_span = html_Soup.find('div', class_='pagenavi').find_all('span')[-2].get_text() ##查找所有的<span>标签获取第十个的<span>标签中的文本也就是最后一个页面了。
for page in range(1, int(max_span)+1): ##不知道为什么这么用的小哥儿去看看基础教程吧
page_url = href + '/' + str(page) ##同上
img_html = requests.get(page_url, headers=headers)
img_Soup = BeautifulSoup(img_html.text, 'lxml')
img_url = img_Soup.find('div', class_='main-image').find('img')['src'] ##这三行上面都说过啦不解释了哦
name = img_url[-9:-4] ##取URL 倒数第四至第九位 做图片的名字
img = requests.get(img_url, headers=headers)
f = open(name+'.jpg', 'ab')##写入多媒体文件必须要 b 这个参数!!必须要!!
f.write(img.content) ##多媒体文件要是用conctent哦!
f.close()

然后进行进一步的封装

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
import requests
from bs4 import BeautifulSoup
import os

class mzitu():

def all_url(self, url):
html = self.request(url)##调用request函数把套图地址传进去会返回给我们一个response
all_a = BeautifulSoup(html.text, 'lxml').find('div', class_='all').find_all('a')
for a in all_a:
title = a.get_text()
print(u'开始保存:', title) ##加点提示不然太枯燥了
path = str(title).replace("?", '_') ##我注意到有个标题带有 ? 这个符号Windows系统是不能创建文件夹的所以要替换掉
self.mkdir(path) ##调用mkdir函数创建文件夹!这儿path代表的是标题title哦!!!!!不要糊涂了哦!
href = a['href']
self.html(href) ##调用html函数把href参数传递过去!href是啥还记的吧? 就是套图的地址哦!!不要迷糊了哦!

def html(self, href): ##这个函数是处理套图地址获得图片的页面地址
html = self.request(href)
max_span = BeautifulSoup(html.text, 'lxml').find('div', class='pagenavi').find_all('span')[-2].get_text()
for page in range(1, int(max_span) + 1):
page_url = href + '/' + str(page)
self.img(page_url) ##调用img函数

def img(self, page_url): ##这个函数处理图片页面地址获得图片的实际地址
img_html = self.request(page_url)
img_url = BeautifulSoup(img_html.text, 'lxml').find('div', class_='main-image').find('img')['src']
self.save(img_url)

def save(self, img_url): ##这个函数保存图片
name = img_url[-9:-4]
img = self.request(img_url)
f = open(name + '.jpg', 'ab')
f.write(img.content)
f.close()

def mkdir(self, path): ##这个函数创建文件夹
path = path.strip()
isExists = os.path.exists(os.path.join("D:\mzitu", path))
if not isExists:
print(u'建了一个名字叫做', path, u'的文件夹!')
os.makedirs(os.path.join("D:\mzitu", path))
os.chdir(os.path.join("D:\mzitu", path)) ##切换到目录
return True
else:
print(u'名字叫做', path, u'的文件夹已经存在了!')
return False

def request(self, url): ##这个函数获取网页的response 然后返回
headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}
content = requests.get(url, headers=headers)
return content

Mzitu = mzitu() ##实例化
Mzitu.all_url('http://www.mzitu.com/all') ##给函数all_url传入参数 你可以当作启动爬虫(就是入口)

大功告成!

电影推荐

大学时看了不少电影。在这里给大家推荐一下个人特别喜欢的吧。

先说几部对自己影响特别大的电影吧。在我心里都是神作


《死亡诗社》

清楚地记得这部电影是大二寒假放假前一天晚上在宿舍看的,当时只是看着 这部电影的评分好高啊,然后就找了资源。刚开始看的时候电影比较沉闷,有些无聊,前半段的亮点只有在山洞里展开的那副裸女图了。看到结尾,感觉怪怪的,心情很沉闷,不知道该说些什么,能遇到 基汀 这样的老师是每个学生的幸运。印象最深刻的两个场景,一是 基汀 老师在课堂上带着学生撕掉了他们的教科书,二是上图基汀老师被校长赶走时,学生们不顾老师的 纷纷站上课桌,喊着”captain, my captain” ,以这样的行为来表示对基汀老师的支持。

《飞越疯人院》

人们总是对生活习以为常。对某些规则总是遵守,不问规则的意义,不问为何要遵守,这时候总需要有一个人来打破这规则。让人们知道,哦,还可以这样做。

这是当时我看完电影后写下的影评。看完后心情久久不能平息,可能是因为我生活中太乖了吧,总是循规蹈矩,遵循着生活的规则,没有一点“意外”出现,日子太枯燥,所以看到这样的电影的时候,像是突然在睡梦中被惊醒一样 砰 的一声,炸开来。

《蝙蝠侠:黑暗骑士》

《本杰明·巴顿其事》


《那些年,我们一起追过的女孩》

《大话西游》

《让子弹飞》


《布达佩斯大饭店》


《闻香识女人》

无间道


开心家族


肖申克的救赎


美丽人生

窃听风暴

星际穿越

一一

小森林

美国往事

放牛班的春天

爆裂鼓手

饮食男女

少年时代

Android ListView无数据时显示其他View

大家都遇到过这样的情况,当 ListView 中的数据为空时,需要显示一个 “没有数据” 的 View 。想过一些办法,比如 FrameLayout 来 包含 ListView 和 数据为空时的 View,当有数据时隐藏空 View,没有数据时显示空 View。或者是给 ListView 添加一个 header 或 footer。但是今天看到了一种更简单的方案,下面来介绍一下。

先贴一下今天看到的代码

布局文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="No items." />

</FrameLayout>

Activity

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
public class MainActivity extends ListActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, generateStrings());

setListAdapter(adapter);

}

private String[] generateStrings() {
String[] strings = new String[0];

for (int i = 0; i < strings.length; ++i) {
strings[i] = "String " + i;
}

return strings;
}

}

Activity 是继承自 ListActivity ,到这里代码都能看懂。奇怪的一点是布局文件中 android:id="@android:id/empty" id 是这样写的。在网上查了一下,这个属性的作用就是,当 ListView 关联的 Adapter 的数据为时,就显示这个 id 为 @android:id/empty 的 View。而当数据不为空时,这个 空 View 就不可见。

如果在不是继承自 ListActivity 的话,上面的那个属性没有作用,TextView 会是一直可见的状态。

所以如果是不继承 ListActivity 的情况下,需要在代码中调用 listView.setEmptyView(nullView) 来设置 listView 数据为空时 显示 nullView 。

样例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<ListView
android:id="@+id/myList"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<!-- Here is the view to show if the list is emtpy -->

<TextView
android:id="@+id/myText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="No items." />

</FrameLayout>
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
public class MainActivity extends Activity {

private ListView mListView = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mListView = (ListView) findViewById(R.id.myList);
mListView.setEmptyView(findViewById(R.id.myText));

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, generateStrings());

mListView.setAdapter(adapter);
}

private String[] generateStrings() {
String[] strings = new String[100];

for (int i = 0; i < strings.length; ++i) {
strings[i] = "String " + i;
}
return strings;
}
}

Android与JS交互

最近的项目里用到了混合开发,需要进行 Android 与 JS 的交互,就了解了一下相关知识。

Android 调用 JS 中的方法

布局就不放出来了。

html 代码如下。两个按钮,分别创建两个 script 方法,一个有参,一个无参。

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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>jsandroid_test</title>

<script type="text/javascript" language="javascript">
function showFromHtml(){
document.getElementById("id_input").value = "Java call Html";
}

function showFromHtml2( param ){
document.getElementById("id_input2").value = "Java call Html : " + param;
}
</script>
</head>

<body>

<input id="id_input" style="width: 90%" type="text" value="null" />
<br>
<input type="button" value="JavacallHtml" onclick="window.jsObj.JavacallHtml()" />

<br>
<input id="id_input2" style="width: 90%" type="text" value="null" />
<br>
<input type="button" value="JavacallHtml2" onclick="window.jsObj.JavacallHtml2()" />

</body>
</html>

android 代码

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
private void showWebView(){     // webView与js交互代码  
try {
mWebView = new WebView(this);
setContentView(mWebView);

mWebView.requestFocus(); //触摸焦点起作用,如果不设置,则在点击网页文本输入框时,不能弹出软键盘及不响应其他的一些事件。

// 设置 WebChromeClient
mWebView.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int progress){
JSAndroidActivity.this.setTitle("Loading...");
JSAndroidActivity.this.setProgress(progress);

if(progress >= 80) {
JSAndroidActivity.this.setTitle("JsAndroid Test");
}
}
});

WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true); //支持 JavaScript
webSettings.setDefaultTextEncodingName("utf-8");

mWebView.addJavascriptInterface(getHtmlObject(), "jsObj"); //设置 JS 接口,第一个参数是事件接口实例,第二个参数是 实例 在 JS 中的别名
mWebView.loadUrl("http://192.168.1.121:8080/jsandroid/index.html");
} catch (Exception e) {
e.printStackTrace();
}
}

private Object getHtmlObject(){

Object insertObj = new Object(){
public void JavacallHtml(){
runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.loadUrl("javascript: showFromHtml()");
Toast.makeText(JSAndroidActivity.this, "clickBtn", Toast.LENGTH_SHORT).show();
}
});
}

public void JavacallHtml2(){
runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.loadUrl("javascript: showFromHtml2('IT-homer blog')");
Toast.makeText(JSAndroidActivity.this, "clickBtn2", Toast.LENGTH_SHORT).show();
}
});
}
};

return insertObj;
}

如上所示,在 JS 中新建两个方法。按钮的点击事件是 window.jsObj.JavacallHtml() 其中 window 代表当前界面,jsObj 是 java 里设置的 JS 接口的别名,JavacallHtml() 是 java 里的方法名字。

JS 调用 Android 中的方法

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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>jsandroid_test</title>

<script type="text/javascript" language="javascript">
function showHtmlcallJava(){
var str = window.jsObj.HtmlcallJava();
alert(str);
}

function showHtmlcallJava2(){
var str = window.jsObj.HtmlcallJava2("IT-homer blog");
alert(str);
}
</script>
</head>

<body>

<br>
<input type="button" value="HtmlcallJava" onclick="showHtmlcallJava()" />
<br>
<input type="button" value="HtmlcallJava2" onclick="showHtmlcallJava2()" />
<br>

</body>
</html>

android 代码

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
private void showWebView(){     // webView与js交互代码  
try {
mWebView = new WebView(this);
setContentView(mWebView);

mWebView.requestFocus(); //触摸焦点起作用,如果不设置,则在点击网页文本输入框时,不能弹出软键盘及不响应其他的一些事件。

// 设置 WebChromeClient
mWebView.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int progress){
JSAndroidActivity.this.setTitle("Loading...");
JSAndroidActivity.this.setProgress(progress);

if(progress >= 80) {
JSAndroidActivity.this.setTitle("JsAndroid Test");
}
}
});

WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true); //支持 JavaScript
webSettings.setDefaultTextEncodingName("utf-8");

mWebView.addJavascriptInterface(getHtmlObject(), "jsObj"); //设置 JS 接口,第一个参数是事件接口实例,第二个参数是 实例 在 JS 中的别名
mWebView.loadUrl("http://192.168.1.121:8080/jsandroid/index.html");
} catch (Exception e) {
e.printStackTrace();
}
}

private Object getHtmlObject(){

Object insertObj = new Object(){
public String HtmlcallJava(){
return "Html call Java";
}

public String HtmlcallJava2(final String param){
return "Html call Java : " + param;
}
};

return insertObj;
}

WebViewClient与WebChromeClient的区别

WebViewClient主要帮助WebView处理各种通知、请求事件的,比如:onLoadResource, onPageStart,onPageFinish, onReceiveError, onReceivedHttpAuthRequest, shouldOverrideUrlLoading, 可以进行 url 的跳转链接等处理。

WebChromeClient主要辅助WebView处理JavaScript的对话框、网站图标、网站title、加载进度等比如 onCloseWindow, onCreateWindow,onJsAlert,onProgressChanged, onReceivedTitle 等。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×

keyboard_arrow_up 回到顶端