<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Roboter&#039;s blog</title>
	<atom:link href="http://blog.robotercoding.com/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://blog.robotercoding.com</link>
	<description>又一个 WordPress 博客</description>
	<lastBuildDate>Mon, 17 May 2010 06:23:38 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>mysql状态查询</title>
		<link>http://blog.robotercoding.com/?p=126</link>
		<comments>http://blog.robotercoding.com/?p=126#comments</comments>
		<pubDate>Mon, 17 May 2010 06:23:38 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[网站]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=126</guid>
		<description><![CDATA[一般在mysql下输入show global status，即可看见所有相关状态，有200多项，一项一项列出来，看起来很不方便。
在网上找到了mysqlreport 这个工具，可以分类显示相关状态，很清晰。
解包之后，执行如下命令即可。
./mysqlreport &#8211;user root –password ******
相关文章：通过show status 来优化MySQL数据库
在网上发现了mysqlreport各个项目的详细介绍，查不到最早的出处了，就从nick处转载过来，感谢nick。
MySQLReport 报告说明
2010年05月9日
Report Header
MySQL 5.0.3 uptime 0 0:34:26 Fri Sep 1 19:46:02 2006
MySQL Server 的版本、自上次启动后已经过多少时间、目前 Server 的日期与时间
key report
MySQL Server的Buffer分为Global Buffer和Thread Buffer
计算 Server 至少需使用的总内存数量的方式为：
min_memory_needed = global_buffer + (thread_buffers * max_connection)
MyISAM Storage Engine 将每个 table 分成三个档案储存在硬盘之中.
FRM： 储存这个数据表的结构
MYD： Row Data，也就是你存在 example 数据表里的数据
MYI： 此数据表的索引
Buffer used 380.00k of 512.00M %Used: 0.07
Current 59.32M [...]]]></description>
			<content:encoded><![CDATA[<p>一般在mysql下输入show global status，即可看见所有相关状态，有200多项，一项一项列出来，看起来很不方便。</p>
<p>在网上找到了<a href="http://hackmysql.com/mysqlreport">mysqlreport</a> 这个工具，可以分类显示相关状态，很清晰。</p>
<p>解包之后，执行如下命令即可。</p>
<p>./mysqlreport &#8211;user root –password ******</p>
<p>相关文章：<a href="http://lxneng.javaeye.com/blog/451985">通过show status 来优化MySQL数据库</a></p>
<p>在网上发现了mysqlreport各个项目的详细介绍，查不到最早的出处了，就从nick处转载过来，感谢nick。</p>
<h4><a href="http://www.uini.net/2010/05/mysqlreport-report-describes.html">MySQLReport 报告说明</a></h4>
<p>2010年05月9日</p>
<p>Report Header</p>
<p>MySQL 5.0.3 uptime 0 0:34:26 Fri Sep 1 19:46:02 2006<br />
MySQL Server 的版本、自上次启动后已经过多少时间、目前 Server 的日期与时间</p>
<p><strong>key report</strong><br />
MySQL Server的Buffer分为Global Buffer和Thread Buffer</p>
<p>计算 Server 至少需使用的总内存数量的方式为：<br />
min_memory_needed = global_buffer + (thread_buffers * max_connection)</p>
<p>MyISAM Storage Engine 将每个 table 分成三个档案储存在硬盘之中.</p>
<p>FRM： 储存这个数据表的结构<br />
MYD： Row Data，也就是你存在 example 数据表里的数据<br />
MYI： 此数据表的索引</p>
<p>Buffer used 380.00k of 512.00M %Used: 0.07<br />
Current 59.32M %Usage: 11.59<br />
Write ratio 0.93<br />
Read ratio 0.00</p>
<p><span style="color: #ff6600;"><strong>注意：新版本的key report变成了</strong></span></p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 609px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;"><span style="color: #ff6600;"><strong>Write hit      99.98%</strong></span></div>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 609px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;"><span style="color: #ff6600;"><strong>Read hit       99.92%</strong></span></div>
<p><span style="color: #ff6600;"><strong>Write hit      99.98%</strong></span></p>
<p><span style="color: #ff6600;"><strong>Read hit       99.92%</strong></span></p>
<p><span style="color: #ff6600;"><strong>表示命中率，这样更好理解。</strong></span></p>
<p>1:Buffer used指出 MySQL “曾经” 耗用过的最大内存数量，因此目前 “正在使用” 的内存数量有可能少于（甚至大于）这个数字。MySQL 称此数值为 “High Water Mark”<br />
如果你的 MySQL 已经使用了 80~90% 以上的 Key Buffer，你就应该要调高 key_buffer_size.</p>
<p>2:Current表示 Key_blocks_unused 这个系统变量来决定目前 MySQL “正在使用” 的 Share Key Buffer 大小</p>
<p>索引(Indexes, Keys)主要是在内存内(RAM-Based)进行操作的，索引之所以如此有用有部份原因就归功于它们主要是在 RAM 里面运作，因此拥有极高的存取效能，不像储存在硬盘中的数据存取速度非常慢。然而，不可否认的是 MySQL 终究还是必须从硬盘中将索引读入 RAM 或是将储存在 RAM 中的索引写回硬盘之中</p>
<p>3:Write ratio 标示着 MySQL 将索引写入硬盘与 MySQL 将索引写入 RAM 的比值(Write Ratio = MySQL 将索引写入硬盘的次数 / MySQL 将索引写入 RAM 的次数)。具有接近于1 的Write Ratio 并不是一件很罕见的事，就像 MySQL 官方手册中所说的，如果你的 MySQL 最主要的活动是 Update、Insert 等等，那么 Write Ratio 将会很接近于1</p>
<p>4:Read Ratio 比 Write Ratio 来得重要一些，它标示了 MySQL 从硬盘读取索引与从 RAM 读取索引的比值(Read Ratio = MySQL 从硬盘读取索引的次数 / MySQL 从 RAM 读取索引的次数)。Read Ratio 的值应该要是 0.00 或 0.01，若大于这个值则表示 Server 有问题需要进一步的调查，通常此问题的成因是 Share Key Buffer 设得太小造成 MySQL 需要不断地从硬盘中读取所需要的索引信息，而这个动作是十分没有效率的并且完全抵消了使用索引可以带来的好处。<br />
<strong>Questions Report</strong><br />
Questions是第二重要的信息，因为它可以告诉你 MySQL 到底都在忙些什么事情。Questions 包含了 SQL queries 以及 MySQL protocol communications。大部份的人都只在意 Server 每秒可以处理多少查询(Queries Per Second, QPS)，但若以整个 Server 的观点来考虑，QPS 其实是非常不精确的数值，它无法有效的告诉您 Server 的整体运作状况</p>
<p>Total 98.06k 47.46/s<br />
DMS 81.23k 39.32/s %Total: 82.84<br />
QC Hits 16.58k 8.02/s 16.91<br />
COM_QUIT 200 0.10/s 0.20<br />
Com_ 131 0.06/s 0.13<br />
-Unknown 82 0.04/s 0.08<br />
Slow 0 0.00/s 0.00 %DMS: 0.00<br />
DMS 81.23k 39.32/s 82.84<br />
SELECT 64.44k 31.19/s 65.72 79.33<br />
INSERT 16.75k 8.11/s 17.08 20.61<br />
UPDATE 41 0.02/s 0.04 0.05<br />
REPLACE 0 0.00/s 0.00 0.00<br />
DELETE 0 0.00/s 0.00 0.00<br />
Com_ 131 0.06/s 0.13<br />
change_db 119 0.06/s 0.12<br />
show_fields 9 0.00/s 0.01<br />
show_status 2 0.00/s 0.00</p>
<p>1:Total 表示<br />
纯的记载 MySQL 总共响应过多少查询，第二个字段则记录响应的频率(QPS)</p>
<p>2:Distribution of Total Queries (DTQ):<br />
所有的 Questions 可以大致区分为五个不同的类别：<br />
1.Data Manipulation Statements (DMS)<br />
2.query cache hits (QC Hits)<br />
3.COM_QUIT<br />
4.all other Com_ commands<br />
5.Unknown</p>
<p>理想的情况下，你会希望 MySQL 把大部份的时间都花在 DMS 与 QC Hits 这两个类别，因为这两个类别才是真正在 “完成正事” 的类别</p>
<p>3:Data manipulation statements(DMS) 包含了：ELECT, INSERT, REPLACE, UPDATE, 与 DELETE(技术上来说，其实不只这几个类别但mysqlreport 只会用到这几类)。基本上，你可以将 DMS 想成是 MySQL 真正有在做些 “有用的事” 的情况，因此你会希望 DMS 是 MySQL 最忙着处理的事情。</p>
<p>4:QC Hits 是 MySQL 不需要实际执行 Query 而只要直接从 Query Cache 中即可找到所需数据的次数。拥有高比例的 QC Hits 是让人梦寐以求的事，因为从 Query Cache 直接存取所需要的数据是十分快速且有效率的。然而大部份的 MySQL Server 因为各种原因，而无法具有非常有效率的 Query Cache</p>
<p>5:COM_ 这个类别代表着所有 MySQL 所执行过的指令，通常与 MySQL protocol 相关。在正常的情况下，你会希望这个类别所占的比例越低越好，因为当这个数值很高的时候就表示 MySQL 正忙碌于无关紧要的事情上</p>
<p><strong>Slow</strong><br />
表示它记录了 MySQL 总共执行了多少次 Slow Query。Slow Query 就是指执行所需时间超过某个时间区间的 Query。一般来说 Slow Query 占 Total Questions 的比例应该要低于 0.05，Slow Query 的次数(第一个字段)本身不是很重要，真正需要注意的是</p>
<p>Slow Query 占 Total Questions 的比例，若这比例偏高就代表 Server 有些问题需要解决<br />
DMS 81.23k 39.32/s 82.84<br />
SELECT 64.44k 31.19/s 65.72 79.33<br />
INSERT 16.75k 8.11/s 17.08 20.61<br />
UPDATE 41 0.02/s 0.04 0.05<br />
REPLACE 0 0.00/s 0.00 0.00<br />
DELETE 0 0.00/s 0.00 0.00</p>
<p>DMS 的子分类项目可以告诉我们，这台 MySQL Server 是属于哪一个类型的 MySQL Server，例如它是着重在 SELECT 操作或是 INSERT 操作，大部份的 MySQL Server 都是着重在 SELECT 操作。知道某台 Server 是属于哪一个类型的 MySQL Server 有助于我们思考报表中的其它信息，例如一台着重在 SELECT 操作的 MySQL Server 的 Write Ratio 应该会非常的接近 1，并有着较高的 Lock 时间。同时它也隐含了一个意义，就是也许你可以考虑使用 InnoDB Storage Engine，因为 MySQL 预设采用的 MyISAM Storage Engine 所提供的 Lock 层级只有</p>
<p>Table Lock(只能针对整个数据表锁定)，而 InnoDB 则提供 Row Lock 层级的锁定机制(可只针对特定的 ROW 进行锁定，减少等待时间)。若是着重在 SELECT 操作的 Server，它的 Read Ratio 应该会接近于零，并有着非常低的 Table Lock 时间。</p>
<p><strong>SELECT and Sort Report</strong></p>
<p>Scan 38 0.02/s %SELECT: 0.06<br />
Range 14 0.01/s 0.02<br />
Full join 3 0.00/s 0.00<br />
Range check 0 0.00/s 0.00<br />
Full rng join 0 0.00/s 0.00<br />
Sort scan 14 0.01/s<br />
Sort range 26 0.01/s<br />
Sort mrg pass 0 0.00/s</p>
<p>大致上来说，你只要注意Scan 与 Full Join。Scan 指的是有多少 SELECT statements 造成 MySQL 需要进行 Full Table Scan。Full Join 的意思与 Scan 差不多，但它是适用在多个 Tables 相互 Join 在一起的情况。这二种情况的执行效能都非常的差，因此原则上你会希望这两个数值越低越好。</p>
<p><strong>Query Cache Report</strong></p>
<p>Memory usage 17.81M of 32.00M %Used: 55.66<br />
Block Fragmnt 13.05%<br />
Hits 16.58k 8.02/s<br />
Inserts 48.50k 23.48/s<br />
Prunes 33.46k 16.20/s<br />
Insrt:Prune 1.45:1 7.28/s<br />
Hit:Insert 0.34:1</p>
<p>Query Cache Report 只有在 MySQL 有支持 Query Cache，以及 Query Cache 功能有开启的情况下才会有这段信息出现。</p>
<p>(1)Memory usage 表示此项目指出 Query Cache 的使用状况</p>
<p>(2)Block Fragmnt 表示块碎片,通常它会界于 10%~20% 之间</p>
<p>(3)Hits, Inserts, Prunes</p>
<p><strong>Table Locks Report</strong></p>
<p>Waited 1.01k 0.49/s %Total: 1.24<br />
Immediate 80.04k 38.74/s</p>
<p>这个部份包含了两项信息：第一项是 Waited，代表 MySQL 需要等待以取得 table lock 的次数。第二项是 Immediate，表示 MySQL 不需要等待即可立刻取得 table lock 的次数</p>
<p><strong>Tables Report</strong></p>
<p>Open 107 of 1024 %Cache: 10.45<br />
Opened 118 0.06/s</p>
<p>Tables Report 同样包含了二项信息：第一是 Open，显示目前正开启的 table 数量、总共可开启的最大数量，以及 Table Cache 的使用状况。第二是 Opend，表示截至目前为止 MySQL 总共开启过的 Table 数量，以及除上 Uptime 后的比值。这里有两件事值得注意：首先是Table Cache 的使用状况，100% 的 Table Cache 使用率并不是一件坏事但你可以试着调大 Table Cache 以增进效能。第二是 MySQL 开启Table 的平均速率，若这个值很高则表示您的 table_cache 设得太小了，需要调大一些。一般来说，MySQL 开启 Table 的平均速率最好是小于 1/s。但大于这个数值也不一定就是坏事，有些调校良好且运作的十分有效率的 MySQL Server 其值为 7/s 并使用了 100% 的 Table Cache</p>
<p><strong>Connections Report</strong></p>
<p>Max used 77 of 600 %Max: 12.83<br />
Total 202 0.10/s</p>
<p>Connections Report 所代表的意义与 Tables Report 相似，请各位以此类推。比较需要注意的是：若你发现 Connections 的使用率接近100%，也许你会想调大 max_connections 的值以允许 MySQL 的 Client 建立更多联机。然而，这通常是一种错误。我们常常可以发现很多网络上的数据会教我们要调大 max_connections，但却从来没有给一个明确的理由。事实上，max_connections 的默认值(100)，就算是对于负载十分沉重但有良好调校过的 Server 都已十分足够。MySQL 对于单一联机的数据处理通常只需要零点几秒的时间即可完成，就算是最大只能使用 100 个联机也够让你用上很长一段时间。若是您的 Server 有着非常高的最大联机数(max connections)或是单一联机需要很长时间才可完成，那么问题八成不是 max_connections 的值不够大而是在别的地方，例如 slow queries、索引设计不良、甚至是过于缓慢的 DNS 解析。在您将 max_connections 的值调到 100 以上之前，您应该要先确定真的是因为 Server 过于忙碌而需要调高此数值，而不是其它地方出了问题。每秒平均联机数有可能会很高，事实上，若这个值很高而且 Server 的运作十分顺畅，那么这通常会是一个好现象，无需</p>
<p>担心。大部份 Server 的每秒平均联机数应该都会低于 5/s。</p>
<p><strong>Created Temp Report</strong></p>
<p>Disk table 10 0.00/s<br />
Table 26 0.01/s<br />
File 3 0.00/s</p>
<p>MySQL 可以建立暂时性的数据表，它可建立在硬盘中、档案里、或是 RAM 之中，而 Created Temp Report 则提供了相关的数据供您参考。这些数据大多是相对而言，没有一定的标准，但将暂时性的数据表建立在硬盘中是十分没有效率的，因此 Disk table 的值最好是三者中最小的一个。当暂时性的数据表被建立在硬盘中，表示此数据表没有办法被放进 RAM 里面（因为 tmp_table_size 的值设得不够大）。</p>
<p><strong>Threads, Aborted, Bytes Reports</strong></p>
<p>Running 55 of 77<br />
Cache 0 %Hit: 0.5<br />
Created 201 0.10/s<br />
Slow 0 0.00/s</p>
<p>这几个部份大多没什么好解释的，只有一个项目值得特别说明：第 66 行的最后一个字段(%Hit)。每一个连接到 MySQL 的联机都是由不同的Thread 来处理，当 MySQL 启动时会预先建立一些 Threads 并保留在 Thread Cache 中，如此一来 MySQL 就不用一直忙着建立与删除Threads。但当每秒最大联机数大于 MySQL 的 Thread Cache 时，MySQL 就会进入 Thread Thrash 的状态：它不断地建立新的 Threads 以满足不断增加的联机的需求。当 Thread Thrash 发生时，%Hit 的数值就会降低。在本范例中 %Hit 的值为 0.05%，这是非常不好的，因为它表示几乎每一个新进来的联机都会造成 MySQL 建立新的 Thread。我们可以看到在此范例中造成此现象的原凶就在第 66 行的第一个字段，我们可以发现 Thread Cache 的值为 0，因此 thread_cache_size 的值需要调大</p>
<p><strong>mysqlreport中的innodb部分详解</strong></p>
<blockquote><p>__ InnoDB Buffer Pool __________________________________________________<br />
Usage 7.97M of 8.00M %Used: 99.61<br />
Read hit 100.00%</p>
<p>Pages<br />
Free 2 %Total: 0.39<br />
Data 499 97.46 %Drty: 0.00<br />
Misc 11 2.15<br />
Latched 0 0.00</p>
<p>Reads 101.06M 8.5/s<br />
From file 373 0.0/s 0.00<br />
Ahead Rnd 19 0.0/s<br />
Ahead Sql 13 0.0/s</p>
<p>Writes 860.88k 0.1/s<br />
Flushes 254.62k 0.0/s<br />
Wait Free 0 0/s</p>
<p>__ InnoDB Lock _________________________________________________________<br />
Waits 424 0.0/s<br />
Current 0<br />
Time acquiring<br />
Total 254266 ms<br />
Average 599 ms<br />
Max 39559 ms</p>
<p>__ InnoDB Data, Pages, Rows ____________________________________________<br />
Data<br />
Reads 502 0.0/s<br />
Writes 344.09k 0.0/s<br />
fsync 158.03k 0.0/s<br />
Pending<br />
Reads 0<br />
Writes 0<br />
fsync 0</p>
<p>Pages<br />
Created 699 0.0/s<br />
Read 523 0.0/s<br />
Written 254.62k 0.0/s</p>
<p>Rows<br />
Deleted 4.59k 0.0/s<br />
Inserted 74.16k 0.0/s<br />
Read 94.67M 8.0/s<br />
Updated 40.61k 0.0/s</p></blockquote>
<p>第一区块, 展示了 mysql innodb 的缓存统计信息.<br />
其中, innodb跟myisam的缓存机制有较大区别, innodb不仅缓存索引,还缓存一些表数据.而myisam只缓存索引.</p>
<blockquote><p>Usage 7.97M of 8.00M %Used: 99.61<br />
Read hit 100.00%</p></blockquote>
<p>Usage 表示, 总的缓存中, 当前已占用 7.97M, 使用率达 99.61%. 这种情况, 是存在瓶颈的, 需要适当增加缓存总量.<br />
Read hit 表示缓存命中率 100%, 这个数值是比较理想的, 一般情况下, 都应该大于 99.98%.</p>
<blockquote><p>Pages<br />
Free 2 %Total: 0.39<br />
Data 499 97.46 %Drty: 0.00<br />
Misc 11 2.15<br />
Latched 0 0.00</p></blockquote>
<p>innodb的存储是按页分的, 每页的容量默认是 16K. (详见 http://www.mysqlperformanceblog.com/2006/06/04/innodb-page-size/)</p>
<p>这里的,Free指的是缓存中的总页数, 剩余的页, 占总的 0.39%.<br />
Data是指缓存中, 存储索引数据的页的数量.其中, 最后一项 %Dtry 表示脏数据的百分比.所谓的脏数据是指, 对缓存数据更新后, 没有同步到硬盘的数据.<br />
misc 和 latched 就是之前说的其他信息的一些缓存.</p>
<blockquote><p>Reads 101.06M 8.5/s<br />
From file 373 0.0/s 0.00<br />
Ahead Rnd 19 0.0/s<br />
Ahead Sql 13 0.0/s</p></blockquote>
<p>Reads代表从缓存里, 总共读取了多少M的数据.<br />
From file, 表示从硬盘文件中读取到缓存里的页数量.<br />
Ahead Rnd, 表示随机预读的次数.<br />
Ahead Sql, 表示全表扫描时, sql预读的次数.</p>
<blockquote><p>Writes 860.88k 0.1/s<br />
Flushes 254.62k 0.0/s<br />
Wait Free 0 0/s</p></blockquote>
<p>Writes , 表示写入缓存的总大小.<br />
Flushes , 表示缓存数据更新到硬盘的大小.<br />
Waint Free, 表示, 等待可写入数据的页的次数.<br />
这里为什么会等待, 是因为, 数据写入到缓存页时, 必须保证, 这个页被创建好, 或者这个页之前的数据已经被同步到硬盘上.</p>
<blockquote><p>Waits 424 0.0/s<br />
Current 0<br />
Time acquiring<br />
Total 254266 ms<br />
Average 599 ms<br />
Max 39559 ms</p></blockquote>
<p>Waits , 表示执行线程等待锁的释放的次数.<br />
Current, 表示当前所有的执行线程, 正在等待锁的数量.<br />
Time acquiring 里的 Total , 指的是, 等待锁所消耗的总时间.<br />
Average, 是指, 平均消耗的时间.<br />
Max, 是指, 单次等待锁, 所消耗最多的时间.</p>
<blockquote><p>Data<br />
Reads 502 0.0/s<br />
Writes 344.09k 0.0/s<br />
fsync 158.03k 0.0/s<br />
Pending<br />
Reads 0<br />
Writes 0<br />
fsync 0</p></blockquote>
<p>Data里的Reads, 表示数据读取的次数.<br />
Writes, 表示数据写入的数据量大小.<br />
fsync, 表示缓存同步到硬盘的数据量大小.</p>
<p>Pending里的Reads, Writes, fsync , 表示当前进行读写, 同步的次数.</p>
<blockquote><p>Pages<br />
Created 699 0.0/s<br />
Read 523 0.0/s<br />
Written 254.62k 0.0/s</p></blockquote>
<p>Pages 里的created, 表示, 总共创建过 699 个页.<br />
Read, 表示被读取的页的数量.<br />
Written, 表示写入到页里的,总大小.</p>
<blockquote><p>Rows<br />
Deleted 4.59k 0.0/s<br />
Inserted 74.16k 0.0/s<br />
Read 94.67M 8.0/s<br />
Updated 40.61k 0.0/s</p></blockquote>
<p>Rows里的四个操作,分别代表删除, 插入, 查询, 更新的数据量大小.</p>
<p><strong>性能关注点分析</strong><br />
首先是Usage, 如果占比达 90 – 95%以上, 可能需要增加预设缓存大小.<br />
调整的参数是 innodb_buffer_pool_size</p>
<p>其次是 Read Hit, 即缓存命中率, 如果该值远远小于100%, 就要调查缓存的有效性了.<br />
比如, 缓存写次数是否大于查询次数. (Buffer里的reads和writes)</p>
<p>最后,需要关注的是, 数据的读写比例. 这里有个要注意的地方是, mysql如果启动到现在不到24小时或一个较长的运行周期, 这个读写比例值可能是不准的.</p>
<p>一般的应用,我感觉读写比例在 8/2 差不多. 如果读远远大于写, 那么你可以测下 MyISAM 引擎的性能, 看看是否适合你.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=126</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>django打包成客户端软件</title>
		<link>http://blog.robotercoding.com/?p=124</link>
		<comments>http://blog.robotercoding.com/?p=124#comments</comments>
		<pubDate>Mon, 23 Nov 2009 08:34:16 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[django]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=124</guid>
		<description><![CDATA[用django做了一个网站，希望断网的时候，也能够在本地使用。google的gears功能很弱，需要一大堆js来操作sqlite数据库，感觉很麻烦。因此还是需要自己弄一个客户端软件。
py2exe可以提供打包功能，将python打包成一个可执行的exe文件。google了一下“django py2exe” ，已经有了很多详细的文档。这个老外的博客比较详细，按照这个流程下来就基本搞定了。
首先确定要打的包：

django
cherrypy，是多线程的http服务器，替换django的http测试服务器
自己的app
python的email

编写一个用于启动的python文件，从原作者处拷贝过来，稍微做了一些修改，命名为startdesktop.py
import os     import sys 
os.environ[ 'DJANGO_SETTINGS_MODULE' ] = &#34;settings&#34; 
import settings     from cherrypy import wsgiserver      import cherrypy      import webbrowser      from django.core.handlers.wsgi import WSGIHandler      from django.core.servers.basehttp [...]]]></description>
			<content:encoded><![CDATA[<p>用django做了一个网站，希望断网的时候，也能够在本地使用。google的gears功能很弱，需要一大堆js来操作sqlite数据库，感觉很麻烦。因此还是需要自己弄一个客户端软件。</p>
<p>py2exe可以提供打包功能，将python打包成一个可执行的exe文件。google了一下“django py2exe” ，已经有了很多详细的文档。这个<a href="http://misunderstandings.wordpress.com/2008/06/26/django-desktop-app/">老外的博客</a>比较详细，按照这个流程下来就基本搞定了。</p>
<p>首先确定要打的包：</p>
<ol>
<li>django</li>
<li>cherrypy，是多线程的http服务器，替换django的http测试服务器</li>
<li>自己的app</li>
<li>python的email</li>
</ol>
<p>编写一个用于启动的python文件，从<a href="http://misunderstandings.wordpress.com/2008/06/26/django-desktop-app/">原作者处</a>拷贝过来，稍微做了一些修改，命名为startdesktop.py</p>
<blockquote><p>import os     <br />import sys </p>
<p>os.environ[ 'DJANGO_SETTINGS_MODULE' ] = &quot;settings&quot; </p>
<p>import settings     <br />from cherrypy import wsgiserver      <br />import cherrypy      <br />import webbrowser      <br />from django.core.handlers.wsgi import WSGIHandler      <br />from django.core.servers.basehttp import AdminMediaHandler </p>
<p>if __name__ == &quot;__main__&quot;:     <br />&#160; print &#8216;To exit DemoSite close this window.&#8217; </p>
<p>&#160; # Set up site-wide config first so we get a log if errors occur.     <br />&#160; cherrypy.config.update({      <br />&#160;&#160;&#160; &#8216;environment&#8217;: &#8216;production&#8217;,      <br />&#160;&#160;&#160; &#8216;log.error_file&#8217;: &#8217;site.log&#8217;,      <br />&#160;&#160;&#160; &#8216;log.screen&#8217;: True,      <br />&#160; }) </p>
<p>&#160; # run CherryPy and open browser     <br />&#160; try:      <br />&#160;&#160;&#160; full_media_path = os.path.dirname( os.path.abspath( sys.argv[ 0 ])) + settings.ADMIN_MEDIA_PREFIX </p>
<p>&#160;&#160;&#160; cherrypy.tree.graft(     <br />&#160;&#160;&#160;&#160;&#160; AdminMediaHandler(      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; WSGIHandler(),      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; media_dir=full_media_path      <br />&#160;&#160;&#160;&#160;&#160; ),      <br />&#160;&#160;&#160;&#160;&#160; &#8216;/&#8217;      <br />&#160;&#160;&#160; )      <br />&#160;&#160;&#160; cherrypy.server.socket_port = settings.WEB_SERVER_PORT      <br />&#160;&#160;&#160; cherrypy.server.start()      <br />&#160;&#160;&#160; cherrypy.engine.start_with_callback( webbrowser.open, ( settings.START_URL,), )&#160;&#160;&#160; <br />&#160;&#160;&#160; cherrypy.engine.block()      <br />&#160; except KeyboardInterrupt:      <br />&#160;&#160;&#160; cherrypy.server.stop()</p>
</blockquote>
<p>再然后就是py2exe的setup.py文件的编写了，基本上照抄下来，原作者使用的django版本很老,有一些模块已经没有了，同时又增加了很多新模块。同时目前的网站代码还是比较多，要手工一个个写是在太麻烦，就写一个函数搜一下，自动把当前的app文件添加进去。同时用正则表达式查找导入的django模块，发现一个就自动添加一个。修改后的setup.py文件如下：</p>
<blockquote><p># -*- coding: utf-8 -*-     <br />from distutils.core import setup      <br />import py2exe      <br />import os </p>
<p>def add_path_tree( base_path, path, skip_dirs=[ '.svn', '.git' ]):     <br />&#160; path = os.path.join( base_path, path )      <br />&#160; partial_data_files = []      <br />&#160; for root, dirs, files in os.walk( os.path.join( path )):      <br />&#160;&#160;&#160; sample_list = []      <br />&#160;&#160;&#160; for skip_dir in skip_dirs:      <br />&#160;&#160;&#160;&#160;&#160; if skip_dir in dirs:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; dirs.remove( skip_dir )      <br />&#160;&#160;&#160; if files:      <br />&#160;&#160;&#160;&#160;&#160; for filename in files:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; sample_list.append( os.path.join( root, filename ))      <br />&#160;&#160;&#160; if sample_list:      <br />&#160;&#160;&#160;&#160;&#160; partial_data_files.append((      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; root.replace(      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; base_path + os.sep if base_path else &#8221;,      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8221;,      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 1      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; ),      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; sample_list      <br />&#160;&#160;&#160;&#160;&#160; ))      <br />&#160; return partial_data_files </p>
<p>def add_python_file(includes,curpath,prefix=&#8221;):     <br />&#160;&#160;&#160; files = os.listdir(curpath)      <br />&#160;&#160;&#160; for f in files:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; fpath = os.path.join(curpath, f)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if os.path.isdir(fpath):      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if f == &#8216;.svn&#8217;:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; continue      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; add_python_file(includes,fpath,prefix+&#8217;%s.&#8217; % f)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; else:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; name,ext = os.path.splitext(f)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if ext == &#8216;.py&#8217;:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; includes.append(&#8217;%s%s&#8217; % (prefix,name))      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; add_django_import(includes,fpath) </p>
<p>import re     <br />pat = r&#8217;from +django\.(.*?) +import +(.*?)( +as +|\n)&#8217;      <br />def add_django_import(includes,filepath):      <br />&#160;&#160;&#160; #print filepath      <br />&#160;&#160;&#160; f = open(filepath,&#8217;r')      <br />&#160;&#160;&#160; data = f.read()      <br />&#160;&#160;&#160; f.close()      <br />&#160;&#160;&#160; models = re.findall(pat,data)      <br />&#160;&#160;&#160; for m in models:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; prefix = &#8216;django.%s.&#8217; % m[0]      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; pys = m[1].split(&#8217;,')      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; for p in pys:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; p = p.lower().strip()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; dmodel = &#8216;%s%s&#8217; % (prefix,p)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; add_django_file(includes,dmodel)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; prefix = &#8216;django.%s&#8217; % m[0]      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; add_django_file(includes,prefix) </p>
<p>def add_django_file(includes,dmodel):     <br />&#160;&#160;&#160; if dmodel in includes:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; return      <br />&#160;&#160;&#160; item = dmodel.replace(&#8217;.',&#8217;/')      <br />&#160;&#160;&#160; mpath = os.path.join(django_path,item)      <br />&#160;&#160;&#160; if os.path.exists(mpath):      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; print &#8216;1&#8242;,dmodel      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; includes.append(dmodel)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; #如果是django的目录，则继续寻找      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; mpath = os.path.join(mpath,&#8217;__init__.py&#8217;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; #print mpath      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if os.path.exists(mpath):      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; add_django_import(includes,mpath)      <br />&#160;&#160;&#160; else:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; mpath = os.path.join(dja<br />
ngo_path,&#8217;%s.py&#8217; % item)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if os.path.exists(mpath):      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; #如果是django的文件，则继续寻找      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; print &#8216;2&#8242;,dmodel      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; #print mpath      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; includes.append(dmodel)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; add_django_import(includes,mpath)</p>
<p>py2exe_options = {      <br />&#160; &#8216;py2exe&#8217;: {      <br />&#160;&#160;&#160; &#8216;compressed&#8217;: 1,      <br />&#160;&#160;&#160; &#8216;optimize&#8217;: 2,      <br />&#160;&#160;&#160; &#8216;ascii&#8217;: 1,      <br />&#160;&#160;&#160; &#8216;bundle_files&#8217;: 1,      <br />&#160;&#160;&#160; &#8216;dist_dir&#8217;: &#8216;../setup-client&#8217;,      <br />&#160;&#160;&#160; &#8216;packages&#8217;: [ 'encodings' ],      <br />&#160;&#160;&#160; &#8216;excludes&#8217; : [      <br />&#160;&#160;&#160;&#160;&#160; 'pywin',      <br />&#160;&#160;&#160;&#160;&#160; 'pywin.debugger',      <br />&#160;&#160;&#160;&#160;&#160; 'pywin.debugger.dbgcon',      <br />&#160;&#160;&#160;&#160;&#160; 'pywin.dialogs',      <br />&#160;&#160;&#160;&#160;&#160; 'pywin.dialogs.list',      <br />&#160;&#160;&#160;&#160;&#160; 'Tkconstants',      <br />&#160;&#160;&#160;&#160;&#160; 'Tkinter',      <br />&#160;&#160;&#160;&#160;&#160; 'tcl',      <br />&#160;&#160;&#160; ],      <br />&#160;&#160;&#160; &#8216;dll_excludes&#8217;: [ 'w9xpopen.exe', 'MSVCR71.dll' ],      <br />&#160;&#160;&#160; &#8216;includes&#8217;: [      <br />&#160;&#160;&#160; 'django.template.loaders.filesystem',      <br />&#160;&#160;&#160; 'django.template.loaders.app_directories',       <br />&#160;&#160;&#160;&#160;&#160; 'django.middleware.common',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.auth.backends',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.sessions.middleware',&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.middleware.doc',&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.contenttypes',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.sessions',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.sessions.backends.db',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.sites',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.admin',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.core.cache.backends',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.db.backends.sqlite3.base',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.db.backends.sqlite3.introspection',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.db.backends.sqlite3.creation',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.db.backends.sqlite3.client',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.template.defaulttags',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.template.defaultfilters',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.template.loader_tags',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.conf.urls.defaults',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.admin.views.main',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.core.context_processors',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.auth.management',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.auth.management.commands',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.auth.handlers',&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.auth',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.auth.middleware',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.views.static',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.admin.templatetags.adminmedia',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.admin.templatetags.admin_list',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.admin.templatetags.admin_modify',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.contrib.admin.templatetags.log',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.conf.urls.shortcut',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.views.defaults',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.core.cache.backends.locmem',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.templatetags.i18n',      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'django.views.i18n',      <br />&#160;&#160;&#160;&#160;&#160; # also used by django?      <br />&#160;&#160;&#160;&#160;&#160; 'email.mime.audio',      <br />&#160;&#160;&#160;&#160;&#160; 'email.mime.base',      <br />&#160;&#160;&#160;&#160;&#160; 'email.mime.image',      <br />&#160;&#160;&#160;&#160;&#160; 'email.mime.message',      <br />&#160;&#160;&#160;&#160;&#160; 'email.mime.multipart',      <br />&#160;&#160;&#160;&#160;&#160; 'email.mime.nonmultipart',      <br />&#160;&#160;&#160;&#160;&#160; 'email.mime.text',      <br />&#160;&#160;&#160;&#160;&#160; 'email.charset',      <br />&#160;&#160;&#160;&#160;&#160; 'email.encoders',      <br />&#160;&#160;&#160;&#160;&#160; 'email.errors',      <br />&#160;&#160;&#160;&#160;&#160; 'email.feedparser',      <br />&#160;&#160;&#160;&#160;&#160; 'email.generator',      <br />&#160;&#160;&#160;&#160;&#160; 'email.header',      <br />&#160;&#160;&#160;&#160;&#160; 'email.iterators',      <br />&#160;&#160;&#160;&#160;&#160; 'email.message',      <br />&#160;&#160;&#160;&#160;&#160; 'email.parser',      <br />&#160;&#160;&#160;&#160;&#160; 'email.utils',      <br />&#160;&#160;&#160;&#160;&#160; 'email.base64mime',      <br />&#160;&#160;&#160;&#160;&#160; 'email.quoprimime',      <br />&#160;&#160;&#160;&#160;&#160; 'traceback',      <br />&#160;&#160;&#160; ],      <br />&#160; }      <br />} </p>
<p># Take the first value from the environment variable PYTHON_PATH     <br />python_path = os.environ[ 'PYTHONPATH' ].split( &#8216;;&#8217; )[ 0 ] </p>
<p>django_path = os.path.normpath( python_path + &#8216;/lib/site-packages/&#8217;) </p>
<p>django_admin_path = os.path.normpath( python_path + &#8216;/lib/site-packages/django/contrib/admin&#8217; )     <br />py2exe_data_files = [] </p>
<p>lc_path = os.path.normpath( python_path + &#8216;/lib/site-packages/django/conf/locale&#8217;) </p>
<p># django admin files     <br />py2exe_data_files += add_path_tree( django_admin_path, &#8216;templates&#8217; )      <br />py2exe_data_files += add_path_tree( django_admin_path, &#8216;media&#8217; )      <br /># project files      <br />py2exe_data_files += add_path_tree( &#8221;, &#8216;newmedia&#8217; )      <br />py2exe_data_files += add_path_tree( &#8221;, &#8216;db&#8217; ) </p>
<p>exedict = py2exe_options['py2exe']     <br />includes = exedict['includes']      <br />curpath = os.path.dirname(__file__)      <br />add_python_file(includes,curpath) </p>
<p>django_auth_path = os.path.normpath( python_path + &#8216;/lib/site-packages/django/contrib/auth&#8217; )     <br />add_python_file(includes,django_auth_path,prefix=&#8217;django.contrib.auth.&#8217;)      <br />add_python_file(includes,django_admin_path,prefix=&#8217;django.contrib.admin.&#8217;) </p>
<p>print includes </p>
<p>setup(     <br />&#160; options=py2exe_options,      <br />&#160; data_files=py2exe_data_files,      <br />&#160; zipfile = None,      <br />&#160; console=[ 'startdesktop.py' ],      <br />)</p>
</blockquote>
<p>最后再执行setup.py py2exe就开始打包了，最后得到的exe为6M多，有点大。</p>
<p>在这个过程中，还会碰到其他问题：</p>
<ol>
<li>template文件太多，不能暴露出去，这个时候可以预先装载template文件到一个py文件中，再编写一个load_template_source函数，放到settings中的TEMPLATE_LOADERS，用于模板查找就可以了。</li>
<li>admin界面不能<br />
使用，因为django使用imp来查找app的admin.py文件，而imp不能搜索.zip文件。可以使用<a href="http://woobiz.com.ar/en/articles/django-and-py2exe-replacing-desktop-with-intranet-applications">woobiz</a>的方法解决,这个暂时还没有试验。</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=124</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>网站的页面优化</title>
		<link>http://blog.robotercoding.com/?p=123</link>
		<comments>http://blog.robotercoding.com/?p=123#comments</comments>
		<pubDate>Mon, 02 Nov 2009 08:23:39 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[网站]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=123</guid>
		<description><![CDATA[网站上线之后，添加到了Google的网站管理员工具。发现google从网站内部提取出了一些奇怪的关键字，比如padding，style，class，这些都是html的标签属性，不应该可见的，难道页面有问题，导致google分析页面出现问题？正好要调整一下页面，所以就一起来弄了。
首先整个网站的结构需要进行调整，原先的html页面内写了太多的style了，有太多的padding了，现在把风格统统调整到一个单独的css文件中。原先在张沈鹏的http://kanrs.com/网站上见过相关介绍，这两天网站好像无法访问了。从http://blog.html.it/layoutgala/获得比较简单的结构，采用css就可以调整各个版块的布局，很方便。

&#60;div id=&#34;container&#34;&#62;        &#160; &#60;div id=&#34;header&#34;&#62;Header&#60;/div&#62;         &#160; &#60;div id=&#34;wrapper&#34;&#62;         &#160;&#160;&#160; &#60;div id=&#34;content&#34;&#62;Content&#60;/div&#62;         &#160; &#60;/div&#62;         &#160; &#60;div id=&#34;navigation&#34;&#62;Navigation&#60;/div&#62; [...]]]></description>
			<content:encoded><![CDATA[<p>网站上线之后，添加到了Google的网站管理员工具。发现google从网站内部提取出了一些奇怪的关键字，比如padding，style，class，这些都是html的标签属性，不应该可见的，难道页面有问题，导致google分析页面出现问题？正好要调整一下页面，所以就一起来弄了。</p>
<p>首先整个网站的结构需要进行调整，原先的html页面内写了太多的style了，有太多的padding了，现在把风格统统调整到一个单独的css文件中。原先在张沈鹏的<a href="http://kanrs.com/">http://kanrs.com/</a>网站上见过相关介绍，这两天网站好像无法访问了。从<a href="http://blog.html.it/layoutgala/">http://blog.html.it/layoutgala/</a>获得比较简单的结构，采用css就可以调整各个版块的布局，很方便。</p>
<blockquote><p><code>
<p>&lt;div id=&quot;container&quot;&gt;        <br />&#160; &lt;div id=&quot;header&quot;&gt;Header&lt;/div&gt;         <br />&#160; &lt;div id=&quot;wrapper&quot;&gt;         <br />&#160;&#160;&#160; &lt;div id=&quot;content&quot;&gt;Content&lt;/div&gt;         <br />&#160; &lt;/div&gt;         <br />&#160; &lt;div id=&quot;navigation&quot;&gt;Navigation&lt;/div&gt;         <br />&#160; &lt;div id=&quot;extra&quot;&gt;Extra stuff&lt;/div&gt;         <br />&#160; &lt;div id=&quot;footer&quot;&gt;Footer&lt;/div&gt;         <br />&lt;/div&gt; </p>
<p>   </code></p></blockquote>
<p>采用这个模板的一个好处是可以把页面正文放在前面，让搜索引擎能够尽快找到真正的内容。原先的结构是把navigation菜单内容放到了前面，结果baidu认为navigation菜单内容比较重要，显示出来了，google比较智能，能够识别真正的内容。</p>
<p>另外一个定宽的css模板推荐使用<a href="http://www.spry-soft.com/grids/">Variable Grid System</a>，可以自动生成所需要的css模板。我们的网站不需要定宽，就不采用这个960 Grid了。</p>
<p>结构调整之后，就开始移除padding，style等标签属性了。这个问题不大，慢慢来弄就好了。另外发现在html页面中直接展示了“&gt;”字符，这是一个闭合符号，应该用“&amp;gt;”替代的，不知道这是否是导致google解析有误的地方？这些地方也要修改。</p>
<p>firefox的插件web developer真的不错，可以检查很多内容。在网页信息中，让它显示了一下排版信息，结果看到了很多粉红色的方块，拿douban的网页来看，基本上没有粉红色方块。我猜应该是dom的层次太多，导致了粉红色。开始对div进行调整，没有必要的就去掉，能够合并的就合并，div层次减少之后，再来看看，没有粉红色了。</p>
<p>web developer还可以检验html页面是否符合xhtml规范，html规范等，选择工具，validator HTML，则会跳到<a href="http://validator.w3.org/">Markup Validation Service</a>，可以进行完整的检验，利用这个工具，发现了一个未闭合的标签。还发现一些属性没有加上引号。修改之后，错误减少了很多，剩下的错误主要是图片标签没有加alt属性了。</p>
<p>另外也看了一些seo的书籍，查看了<a href="http://www.google.com/support/webmasters/">网站管理员/站长 帮助</a>，调整了页面的title，keyword，description等等。感觉这次调整差不多了，先更新到网站上看看效果。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=123</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>屏蔽ip地址</title>
		<link>http://blog.robotercoding.com/?p=111</link>
		<comments>http://blog.robotercoding.com/?p=111#comments</comments>
		<pubDate>Wed, 14 Oct 2009 14:31:50 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[linux]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=111</guid>
		<description><![CDATA[在nginx中可以直接屏蔽某些IP地址，加上如下语句即可：
deny 221.194.132.0/24;
这样这些ip访问网站的时候，会得到403错误，但有一个问题，访问记录会保存到error.log中，有些垃圾爬虫得到了403错误，还是不断访问，导致log很大，好在可以使用iptables屏蔽：
sudo iptables -A INPUT -i eth0 -j DROP -p tcp --dport 80 -s 221.194.132.0/24
这样nginx也可以轻松一些了。
]]></description>
			<content:encoded><![CDATA[<p>在nginx中可以直接屏蔽某些IP地址，加上如下语句即可：</p>
<blockquote><p><code>deny 221.194.132.0/24;</code></p></blockquote>
<p>这样这些ip访问网站的时候，会得到403错误，但有一个问题，访问记录会保存到error.log中，有些垃圾爬虫得到了403错误，还是不断访问，导致log很大，好在可以使用iptables屏蔽：</p>
<blockquote><p><code>sudo iptables -A INPUT -i eth0 -j DROP -p tcp --dport 80 -s 221.194.132.0/24</code></p></blockquote>
<p>这样nginx也可以轻松一些了。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=111</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>django 发送错误报告</title>
		<link>http://blog.robotercoding.com/?p=110</link>
		<comments>http://blog.robotercoding.com/?p=110#comments</comments>
		<pubDate>Tue, 13 Oct 2009 05:28:33 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[django]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=110</guid>
		<description><![CDATA[网站发布之后，需要设置debug=false。这时候如果发生500错误，就不能直接看到出错信息了，django会通过email发送相关的错误信息。需要在settings中进行相关设置即可。老外有一篇文章写得很详细，主要设置如下字段：
EMAIL_HOST=’smtp.xxx.com’     EMAIL_PORT=25      EMAIL_HOST_USER=’my_mailbox_name’      EMAIL_HOST_PASSWORD=’my_mailbox_password’      SERVER_EMAIL=’webmaster@yyy.com’      ADMINS=( (’PK Shiu’, ’support@yyy.com’),)      DEBUG=False

gmail功能强大，可以使用gmail来发送，可以新注册一个gmail信箱专门用于发送错误报告。在settings中需要再加一个设置：
EMAIL_USE_TLS = True

]]></description>
			<content:encoded><![CDATA[<p>网站发布之后，需要设置debug=false。这时候如果发生500错误，就不能直接看到出错信息了，django会通过email发送相关的错误信息。需要在settings中进行相关设置即可。老外有<a href="http://www.pkshiu.com/loft/archive/2009/03/django-production-error-handler">一篇文章</a>写得很详细，主要设置如下字段：</p>
<blockquote><p>EMAIL_HOST=’smtp.xxx.com’     <br />EMAIL_PORT=25      <br />EMAIL_HOST_USER=’my_mailbox_name’      <br />EMAIL_HOST_PASSWORD=’my_mailbox_password’      <br /><a href="mailto:SERVER_EMAIL=&rsquo;webmaster@yyy.com&rsquo;">SERVER_EMAIL=’webmaster@yyy.com’</a>      <br />ADMINS=( (’PK Shiu’, <a href="mailto:&rsquo;support@yyy.com&rsquo;">’support@yyy.com’</a>),)      <br />DEBUG=False</p>
</blockquote>
<p>gmail功能强大，可以使用gmail来发送，可以新注册一个gmail信箱专门用于发送错误报告。在settings中需要再加一个设置：</p>
<blockquote><p>EMAIL_USE_TLS = True</p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=110</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>mysql错误</title>
		<link>http://blog.robotercoding.com/?p=107</link>
		<comments>http://blog.robotercoding.com/?p=107#comments</comments>
		<pubDate>Fri, 01 May 2009 16:20:39 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[linux]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=107</guid>
		<description><![CDATA[ubuntu8.04下使用apt-get安装mysql的缺省版本是5.0.5,安装完成时候，出现一些问题，无法启动mysqld，提示如下：
* Checking for corrupt, not cleanly closed and upgrade needing tables.

查了一下，好像没有什么好的解答，似乎这个提示和无法启动mysqld没有什么关系，查看mysql的log，也没有什么记录下来。所以干脆用源代码来进行安装，下了最新的5.1.34。在安装mysql之前，还需要安装ncurses这个库，安装完成之后，sudo ldconfig即可。
mysql安装完成之后，启动mysql，报如下错误：
Manager of pid-file quit without updating file.

Google之后，找到一个老外的提示，在新安装mysql的时候，需要执行mysql_install_db进行mysql插件的初始化。
&#160;
总结：mysql一般的安装顺序是：

./configure &#8211;prefix=/usr/local/mysql
make
make install
scripts/mysql_install_db
设定权限
chown -R root&#160; /usr/local/mysql     chown -R mysql /usr/local/mysql/var      chgrp -R mysql /usr/local/mysql


]]></description>
			<content:encoded><![CDATA[<p>ubuntu8.04下使用apt-get安装mysql的缺省版本是5.0.5,安装完成时候，出现一些问题，无法启动mysqld，提示如下：</p>
<blockquote><p>* Checking for corrupt, not cleanly closed and upgrade needing tables.</p>
</blockquote>
<p>查了一下，好像没有什么好的解答，似乎这个提示和无法启动mysqld没有什么关系，查看mysql的log，也没有什么记录下来。所以干脆用源代码来进行安装，下了最新的5.1.34。在安装mysql之前，还需要安装<a href="http://www.gnu.org/software/ncurses/">ncurses</a>这个库，安装完成之后，sudo ldconfig即可。</p>
<p>mysql安装完成之后，启动mysql，报如下错误：</p>
<blockquote><p>Manager of pid-file quit without updating file.</p>
</blockquote>
<p>Google之后，找到一个<a href="http://forums.sun.com/thread.jspa?threadID=5339481">老外的提示</a>，在新安装mysql的时候，需要执行mysql_install_db进行mysql插件的初始化。</p>
<p>&#160;</p>
<p>总结：mysql一般的安装顺序是：</p>
<ol>
<li>./configure &#8211;prefix=/usr/local/mysql</li>
<li>make</li>
<li>make install</li>
<li>scripts/mysql_install_db</li>
<li>设定权限</li>
<p>chown -R root&#160; /usr/local/mysql     <br />chown -R mysql /usr/local/mysql/var      <br />chgrp -R mysql /usr/local/mysql</p>
</p>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=107</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>linux操作命令记录</title>
		<link>http://blog.robotercoding.com/?p=106</link>
		<comments>http://blog.robotercoding.com/?p=106#comments</comments>
		<pubDate>Thu, 23 Apr 2009 16:03:37 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[linux]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=106</guid>
		<description><![CDATA[删除大小为0的文件
find . -name &#34;*&#34; -type f -size 0c &#124; xargs -n 1 rm -f

但是如果文件名有空格，则不能删除,但是可以将带空格的文件名改成没有空格，则就可以删除了。
命令如下：
for file in *;do mv &#34;$file&#34; &#34;$(echo $file&#124;tr -d &#8216; &#8216;)&#34;;done

&#160;
修改linux系统本地时间为东八区
cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

&#160;
设定后台进程：
crontab -e      00 00 * * * /bin/bash&#160; /var/xxx.sh

设定nginx自动切割日志：
#!/bin/bash      # This script run at 00:00 
# The Nginx logs [...]]]></description>
			<content:encoded><![CDATA[<p>删除大小为0的文件</p>
<blockquote><p>find . -name &quot;*&quot; -type f -size 0c | xargs -n 1 rm -f</p>
</blockquote>
<p>但是如果文件名有空格，则不能删除,但是可以将带空格的文件名改成没有空格，则就可以删除了。</p>
<p>命令如下：</p>
<blockquote><p>for file in *;do mv &quot;$file&quot; &quot;$(echo $file|tr -d &#8216; &#8216;)&quot;;done</p>
</blockquote>
<p>&#160;</p>
<p>修改linux系统本地时间为东八区</p>
<blockquote><p>cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime</p>
</blockquote>
<p>&#160;</p>
<p>设定后台进程：</p>
<blockquote><p>crontab -e      <br />00 00 * * * /bin/bash&#160; /var/xxx.sh</p>
</blockquote>
<p>设定nginx自动切割日志：</p>
<blockquote><p>#!/bin/bash      <br /># This script run at 00:00 </p>
<p># The Nginx logs path      <br />logs_path=&quot;/var/log/nginx/&quot; </p>
<p>mkdir -p ${logs_path}$(date -d &quot;yesterday&quot; +&quot;%Y&quot;)/$(date -d &quot;yesterday&quot; +&quot;%m&quot;)/      <br />mv ${logs_path}localhost.access.log ${logs_path}$(date -d &quot;yesterday&quot; +&quot;%Y&quot;)/$(date -d &quot;yesterday&quot; +&quot;%m&quot;)/localhost.access_$(date -d &quot;yesterday&quot; +&quot;%Y%m%d&quot;).log       <br />#kill -USR1 `cat /var/run/nginx.pid`       <br />killall &#8211;s USR1 nginx</p>
</blockquote>
<p>查看nginx状态，参考<a title="http://wiki.nginx.org//NginxChsStubStatusModule?highlight=(status)" href="http://wiki.nginx.org//NginxChsStubStatusModule?highlight=(status)">http://wiki.nginx.org//NginxChsStubStatusModule?highlight=(status)</a></p>
<blockquote><pre>location /nginx_status {
 # copied from <a href="http://blog.kovyrin.net/2006/04/29/monitoring-nginx-with-rrdtool/">http://blog.kovyrin.net/2006/04/29/monitoring-nginx-with-rrdtool/
</a> stub_status on;
 access_log   off;
 allow SOME.IP.ADD.RESS;
 deny all;
}</pre>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=106</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>django admin界面处理</title>
		<link>http://blog.robotercoding.com/?p=105</link>
		<comments>http://blog.robotercoding.com/?p=105#comments</comments>
		<pubDate>Wed, 15 Apr 2009 15:42:57 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[django]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=105</guid>
		<description><![CDATA[django的admin很强大，可以很方便管理后台数据。近期的一个网站需要处理一堆关联的数据，如果简单使用admin来管理，会碰到一些问题。
情形如下：有A和B两个model
class A(models.Model):     &#160;&#160;&#160; &#8230; 
class B(models.Model):     &#160;&#160;&#160; a = models.ForeignKey(A)      &#160;&#160;&#160; &#8230;

在admin界面中A比较好编辑，在数据量不多的时候，添加B也方便。但是如果A有了几千条数据，添加B的时候要从这么一堆A数据中选择一条，从可用性和系统性能方面来说都是是根本不可能的。于是django admin提供了raw_id_fields字段，ForeignKey字段使用了input编辑框，用户需要手工输入A对象的id即可，或者在弹出的对话框中对A对象集合进行筛选。
这样处理感觉不够方便，数据维护人员需要记住A的id，这很容易填错id。不希望让维护人员了解这些ID，所以就希望能够在A对象的列表界面加一个&#8220;添加B对象&#8221;字段，当点击这个字段的时候，即可调出B对象的添加界面，django admin支持这种自定义字段，在A对象中如下设置即可：
&#160;&#160;&#160; def new-B(self):     &#160;&#160;&#160;&#160;&#160;&#160;&#160; return u&#8217;&#60;a href=&#34;/admin/xxapp/B/add/?a=%d&#34;&#62;新建 B&#60;/a&#62;&#8217;&#160; %&#160; self.id      &#160;&#160;&#160; new-B.allow_tags = True

通过这种自定义的方式可以添加很多操作，链接会带到任何想执行的功能。
B对象的添加界面出来之后，不希望还要了解A对象的id，因为这是从一个A对象发起的添加操作。所以需要把A对象ID传到B的添加界面中。参数的传递只能通过GET进行了，考察了django.contrib.admin.options,他是支持GET参数的，于是就这样把A对象的ID细节给隐藏掉了。如果怕用户不小心修改了A对象的ID，则可以使用javascript将这个input编辑框给disable掉。
django admin支持添加css和javascript：如下例：
&#160;&#160;&#160; class ArticleAdmin(admin.ModelAdmin):    [...]]]></description>
			<content:encoded><![CDATA[<p>django的admin很强大，可以很方便管理后台数据。近期的一个网站需要处理一堆关联的数据，如果简单使用admin来管理，会碰到一些问题。</p>
<p>情形如下：有A和B两个model</p>
<blockquote><p>class A(models.Model):     <br />&#160;&#160;&#160; &#8230; </p>
<p>class B(models.Model):     <br />&#160;&#160;&#160; a = models.ForeignKey(A)      <br />&#160;&#160;&#160; &#8230;</p>
</blockquote>
<p>在admin界面中A比较好编辑，在数据量不多的时候，添加B也方便。但是如果A有了几千条数据，添加B的时候要从这么一堆A数据中选择一条，从可用性和系统性能方面来说都是是根本不可能的。于是django admin提供了raw_id_fields字段，ForeignKey字段使用了input编辑框，用户需要手工输入A对象的id即可，或者在弹出的对话框中对A对象集合进行筛选。</p>
<p>这样处理感觉不够方便，数据维护人员需要记住A的id，这很容易填错id。不希望让维护人员了解这些ID，所以就希望能够在A对象的列表界面加一个&#8220;添加B对象&#8221;字段，当点击这个字段的时候，即可调出B对象的添加界面，django admin支持这种自定义字段，在A对象中如下设置即可：</p>
<blockquote><p>&#160;&#160;&#160; def new-B(self):     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; return u&#8217;&lt;a href=&quot;/admin/xxapp/B/add/?a=%d&quot;&gt;新建 B&lt;/a&gt;&#8217;&#160; %&#160; self.id      <br />&#160;&#160;&#160; new-B.allow_tags = True</p>
</blockquote>
<p>通过这种自定义的方式可以添加很多操作，链接会带到任何想执行的功能。</p>
<p>B对象的添加界面出来之后，不希望还要了解A对象的id，因为这是从一个A对象发起的添加操作。所以需要把A对象ID传到B的添加界面中。参数的传递只能通过GET进行了，考察了django.contrib.admin.options,他是支持GET参数的，于是就这样把A对象的ID细节给隐藏掉了。如果怕用户不小心修改了A对象的ID，则可以使用javascript将这个input编辑框给disable掉。</p>
<p>django admin支持添加css和javascript：如下例：</p>
<blockquote><p>&#160;&#160;&#160; class ArticleAdmin(admin.ModelAdmin):     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; class Media:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; css = {      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &quot;all&quot;: (&quot;my_styles.css&quot;,)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; js = (&quot;my_code.js&quot;,)</p>
</blockquote>
<p>B添加完成之后，还希望继续添加另外一个B，也是基于同样的A对象，点击&#8220;保存并增加另一个&#8221;，这时候GET信息丢失了，A对象ID没有了。第一反映是希望用javascript把&#8220;保存并增加另一个&#8221;的链接加上GET信息，查看了页面的源代码，是一个submit的操作，没有链接可以修改的。</p>
<blockquote><p>input type=&quot;submit&quot; value=&quot;保存并增加另一个&quot; name=&quot;_addanother&quot; /</p>
</blockquote>
<p>还是到服务端来考察，在django.contrib.admin.options中查找_addanother，在response_add函数中针对_addanother进行了处理，于是事情就简单了，在B的ModelAdmin对象中重写response_add函数：</p>
<blockquote><p>def response_add(self, request, obj, post_url_continue=&#8217;../%s/&#8217;):&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if request.POST.has_key(&quot;_addanother&quot;):      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; from django.utils.translation import ugettext as _      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; from django.utils.encoding import force_unicode      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; from django.http import HttpResponseRedirect      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; opts = obj._meta      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; pk_value = obj._get_pk_val()&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; msg = _(&#8217;The %(name)s &quot;%(obj)s&quot; was added successfully.&#8217;) % {&#8217;name&#8217;: force_unicode(opts.verbose_name), &#8216;obj&#8217;: force_unicode(obj)}&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; self.message_user(request, msg + &#8216; &#8216; + (_(&quot;You may add another %s below.&quot;) % force_unicode(opts.verbose_name)))</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; #新增加代码，记录下GET的信息      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; getinfos = request.GET.items()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; gets = &#8216;?&#8217;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; for info in getinfos:      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; gets += &#8216;%s=%s&amp;&#8217; % (info[0],info[1])      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; path = request.path + gets</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; return HttpResponseRedirect(path)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; return super(admin.ModelAdmin, self).response_add(request,obj,post_url_continue)</p>
</blockquote>
<p>这样GET信息就可以保存下来了。</p>
<p>查看了django的最新版本，GET信息还是没有保存，这个不应该，提了一个ticket上去。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=105</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>python 图片处理</title>
		<link>http://blog.robotercoding.com/?p=93</link>
		<comments>http://blog.robotercoding.com/?p=93#comments</comments>
		<pubDate>Wed, 08 Apr 2009 16:43:44 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=93</guid>
		<description><![CDATA[有一堆gif图片，需要处理，python有一个PIL库，处理起来很方便，但是转换的效果很有问题。如： 这一张透明的gif图片，使用简单的保存语句：
from PIL import Image 
im = Image.open(&#8217;9.gif&#8217;)      transparency = im.info['transparency']       im.save(&#8217;output.gif&#8217; ,transparency=transparency )

得出的output.gif为 ，多了一些乱七八糟的点。怀疑是我的windows系统环境有问题，然后拿到了ubuntu系统上测试，也是如此，不甘心，在ubuntu上下载了一个python 2.5.4，再下载了PIL的源码进行编译，现象依旧。然后再跑到国外的主机上进行测试，也是一样，不过转换出来的大小都不太一样，有几十个字节的差异。
在open之后，我立即打印了im的数据
print list(im.getdata())
按照正常的话，黑白gif应该只有0和1的点，但是发现下面有很多大于1的点：
[0, 0, 0, 0, 192, 48, 30, 30, 241, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 100, 0, 0, 90, 0, 0, 100, 1, [...]]]></description>
			<content:encoded><![CDATA[<p>有一堆gif图片，需要处理，python有一个PIL库，处理起来很方便，但是转换的效果很有问题。如：<img src="http://lh5.ggpht.com/_ZZFjnE6FoMA/SceolpdL9AI/AAAAAAAAACQ/DgjEcarNev4/9.gif" /> 这一张透明的gif图片，使用简单的保存语句：</p>
<blockquote><p>from PIL import Image </p>
<p>im = Image.open(&#8217;9.gif&#8217;)      <br />transparency = im.info['transparency']       <br />im.save(&#8217;output.gif&#8217; ,transparency=transparency )</p>
</blockquote>
<p>得出的output.gif为<img src="http://lh4.ggpht.com/_ZZFjnE6FoMA/SceolmR6NaI/AAAAAAAAACY/zMnCilrLtR0/output.gif" /> ，多了一些乱七八糟的点。怀疑是我的windows系统环境有问题，然后拿到了ubuntu系统上测试，也是如此，不甘心，在ubuntu上下载了一个python 2.5.4，再下载了PIL的源码进行编译，现象依旧。然后再跑到国外的主机上进行测试，也是一样，不过转换出来的大小都不太一样，有几十个字节的差异。</p>
<p>在open之后，我立即打印了im的数据</p>
<p>print list(im.getdata())</p>
<p>按照正常的话，黑白gif应该只有0和1的点，但是发现下面有很多大于1的点：</p>
<blockquote><p>[0, 0, 0, 0, 192, 48, 30, 30, 241, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 100, 0, 0, 90, 0, 0, 100, 1, 0, 100, 2, 0, 107, 1, 0, 90, 1, 0, 100, 1, 0, 100, 2, 0     <br />, 107, 2, 0, 90, 2, 0, 100, 0, 0, 0, 1, 1, 1, 1, 1, 0, 3, 0, 104, 0, 0, 4, 100, 0, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 2, 100, 6, 0, 60, 0, 0, 0, 0, 1, 0, 0, 0, 1, 4,      <br /> 100, 9, 0, 2, 100, 10, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 60, 4, 100, 13, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 100, 15, 0, 60, 90, 4, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 100,      <br /> 17, 0, 101, 3, 0, 105, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 131, 0, 0, 89, 90, 0, 0, 1, 0, 0, 0, 1, 0, 0, 7, 0, 101, 2, 0, 105, 8, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 13      <br />1, 3, 0, 1, 101, 2, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 131, 2, 0, 1, 101, 2, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 131, 2, 0, 1, 101, 2, 0, 105, 10, 0, 100, 20, 0, 100, 22,      <br />0, 131, 2, 0, 1, 101, 2, 0, 105, 10, 0, 100, 20, 0, 100, 23, 0, 131, 2, 0, 1, 100, 2, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]</p>
</blockquote>
<p>尝试将大于1的点清0，再putdata回去，结果是干净了一点，但还有一些污点，可能本来是一些0的点被染成了1。</p>
<p>该gif图片的格式为GIF89a，转存之后为GIF87a。</p>
<p>我又尝试了一些非透明的gif，这样的转存没有问题。难道是PIL对透明gif处理有问题？google了很多文章，都没有发现什么解决方案或者类似的问题，老外写的文章<a href="http://nadiana.com/pil-tips-converting-png-gif" target="_blank">PIL Tips: Converting Between PNG and GIF</a>,倒是有很多例子，不过好像没有碰到我的这个问题。</p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>没辙了，看看有没有其他的图形库。发现了<a title="http://www.imagemagick.org/" href="http://www.imagemagick.org/">imagemagick</a>。有python接口，提供了命令行工具。于是在ubuntu上用sudo apt-get install python-pythonmagick安装，下了一堆包，安装完成，在site-packages目录没有发现任何新东西，不知道给装到哪里去了。当然就无法import PythonMagick了。罢了，还是手工编译安装吧。</p>
<p>imagemagick很顺利的编译安装，听说python接口手工安装非常麻烦，也不用python接口了，直接使用命令行进行处理。看了大量的<a href="http://www.imagemagick.org/Usage/">例子帮助</a>，明白了一些基本用法，直接在命令行输入下列指令：</p>
<blockquote><p>convert 9.gif output.gif</p>
</blockquote>
<p>这回成功了，output.gif和9.gif图片一样的。但是在将png转换为gif的时候报错：</p>
<blockquote><p>convert&#160;&#160;&#160; gtk1.png&#160; gtk1.gif     <br />convert: no decode delegate for this image format</p>
</blockquote>
<p>google了一下，应该是少了一些部件。需要到(<a href="http://www.imagemagick.org/download/delegates/">http://www.imagemagick.org/download/delegates/</a>)下载所缺少的库进行安装，之后imagemagick需要再重新configue ,make install.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=93</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>上传文件的跨域处理</title>
		<link>http://blog.robotercoding.com/?p=62</link>
		<comments>http://blog.robotercoding.com/?p=62#comments</comments>
		<pubDate>Mon, 06 Apr 2009 16:36:02 +0000</pubDate>
		<dc:creator>roboter</dc:creator>
				<category><![CDATA[linux]]></category>

		<guid isPermaLink="false">http://blog.robotercoding.com/?p=62</guid>
		<description><![CDATA[为了减轻主服务器的负担，希望把图片的处理单独拿出来，由图片服务器来处理图片上传，浏览等操作。图片上传使用了ajax方式，采用了ajaxfileupload控件，它动态生成了一个iframe，由iframe来接受上传结果。以前的操作都在www.***.com上处理，这个控件用得不错，分开之后，由img.***.com来负责上传，结果问题就来了，iframe的contentWindow以及contentDocument下面的document拒绝访问。google之后，发现应该是跨域问题，按照道理来说，两个子域的跨域应该很好解决。无非是设置document.domain，主页面中如下设置：
&#60;script language=&#34;text/javascript&#34;&#62;     &#160;&#160;&#160; document.domain = &#34;***.com&#34;;      &#60;/script&#62; 

在iframe中的onload事件函数中，也设置了document.domain = &#34;***.com&#34;;测试了一下，行不通，还是会报&#8220;拒绝访问&#8221;，虽然Firebug已经看到了返回值。对javascript不是很熟悉，不知道iframe中这样设置document.domain是否合法。
然后再google了一堆资料，针对跨域的处理有很多方式：jquery的jsonp技术，动态地添加一个&#60;script&#62;标签，因为script标签的src属性是没有跨域的限制，但是好像只能get，不支持post。还有通过服务器中转，数据先到达www.***.com,由www服务器发送到img服务器进行处理，这样也没有问题，但是数据绕了一圈，费时费力。
这个时候想起了nginx的代理设置，是否能够将www服务器的目录映射成img服务器呢？我想应该可以的，那就尝试一下。
首先给ubuntu装上dns服务器，可以模拟这些域名进行测试。然后配置nginx如下：
upstream hosts_www_com {     &#160;&#160;&#160; server 192.168.1.2:80;      }      server {      &#160;&#160;&#160; listen&#160;&#160;&#160;&#160;&#160;&#160; 80;      &#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>为了减轻主服务器的负担，希望把图片的处理单独拿出来，由图片服务器来处理图片上传，浏览等操作。图片上传使用了ajax方式，采用了<a href="http://www.phpletter.com/Our-Projects/AjaxFileUpload/" target="_blank">ajaxfileupload</a>控件，它动态生成了一个iframe，由iframe来接受上传结果。以前的操作都在www.***.com上处理，这个控件用得不错，分开之后，由img.***.com来负责上传，结果问题就来了，iframe的contentWindow以及contentDocument下面的document拒绝访问。google之后，发现应该是跨域问题，按照道理来说，两个子域的跨域应该很好解决。无非是设置document.domain，主页面中如下设置：</p>
<blockquote><p>&lt;script language=&quot;text/javascript&quot;&gt;     <br />&#160;&#160;&#160; document.domain = &quot;***.com&quot;;      <br />&lt;/script&gt; </p>
</blockquote>
<p>在iframe中的onload事件函数中，也设置了document.domain = &quot;***.com&quot;;测试了一下，行不通，还是会报&#8220;拒绝访问&#8221;，虽然Firebug已经看到了返回值。对javascript不是很熟悉，不知道iframe中这样设置document.domain是否合法。</p>
<p>然后再google了一堆资料，针对跨域的处理有很多方式：jquery的jsonp技术，动态地添加一个&lt;script&gt;标签，因为script标签的src属性是没有跨域的限制，但是好像只能get，不支持post。还有通过服务器中转，数据先到达<a href="http://www.***.com">www.***.com</a>,由www服务器发送到img服务器进行处理，这样也没有问题，但是数据绕了一圈，费时费力。</p>
<p>这个时候想起了nginx的代理设置，是否能够将www服务器的目录映射成img服务器呢？我想应该可以的，那就尝试一下。</p>
<p>首先给ubuntu装上dns服务器，可以模拟这些域名进行测试。然后配置nginx如下：</p>
<blockquote><p>upstream hosts_www_com {     <br />&#160;&#160;&#160; server 192.168.1.2:80;      <br />}      <br />server {      <br />&#160;&#160;&#160; listen&#160;&#160;&#160;&#160;&#160;&#160; 80;      <br />&#160;&#160;&#160; server_name&#160; www.***.com; </p>
<p>&#160;&#160;&#160; access_log&#160; /var/log/nginx/***.host.access.log;&#160;&#160;&#160; </p>
<p>&#160;&#160;&#160; location ~ ^/imgupload/ {     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; proxy_pass&#160;&#160;&#160;&#160;&#160;&#160;&#160; <a href="http://192.168.1.129">http://192.168.1.129;</a>&#160; #图片服务器的ip      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; proxy_set_header&#160; X-Real-IP&#160; $remote_addr; </p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; proxy_redirect off;</p>
<p>&#160;&#160;&#160; } </p>
<p>&#160;&#160;&#160; location / {     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; proxy_redirect off;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; proxy_set_header Host $host;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; proxy_pass <a href="http://hosts_www_com;">http://hosts_www_com;</a>      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; proxy_set_header&#160; x-real-IP&#160; $remote_addr;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;      <br />&#160;&#160;&#160; } </p>
<p>}</p>
</blockquote>
<p>然后再测试一下，OK了。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.robotercoding.com/?feed=rss2&amp;p=62</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
