libcraigscrape 0.5 → 0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,4 +1,18 @@
1
1
  == Change Log
2
2
 
3
+ === Release 0.6.0 (May 21, 2009)
4
+ - Added PostFull::flagged_for_removal?
5
+ - Fixed a couple small parse bugs found in production
6
+ - Added a ParseError object, which is raised on ... detected Parse errors. This raise will output the buggy parse, please send this over to me if you think CraigsScrape is actually wrong ..
7
+ - Adjusted the examples in the readme, added a "require 'rubygems'" to the top of the listing so that they would actually work if you tried to run them verbatim (Thanks J T!)
8
+ - Restructured some of the parsing to be less leinient when scraped values aren't matching their regexp's in the PostSummary
9
+ - It seems like craigslist returns a 404 on pages that exist, for no good reason on occasion. Added a retry mechanism that wont take no for an answer, unless we get a defineable number of them in a row
10
+ - Added CraigScrape cattr_accessors : retries_on_fetch_fail, sleep_between_fetch_retries .
11
+ - Adjusted craigwatch to not commit any database changes until the notification email goes out. This way if there's an error, the user wont miss any results on a re-run
12
+ - Added a FetchError for http requests that don't return 200 or redirect...
13
+ - Adjusted craigwatch to use scrape_until instead of scrape_since, this new approach cuts down on the url fetching by assuming that if we come across something we've already tracked, we dont need to keep going any further. NOTE: We still can't use a 'last_scraped_url' on the TrackedSearch model b/c sometimes posts get deleted.
14
+ - We're tracking all date-relevant posts now in craigwatch, not just the ones that matched the first time aorund, this should significantly speed up our scrapes
15
+ - Found an example of a screwy listing that was starting date h4's , but not actually listing the dates in them (see mia_fua_index8900.5.21.09.html test case). Added test case - handled appropriately. Seems like a bug in craigslist itself.
16
+
3
17
  === Release 0.5.0 (April 30, 2009)
4
- * First release. Not much to say - hopefully someone else finds this useful.
18
+ - First release. Not much to say - hopefully someone else finds this useful.
data/README CHANGED
@@ -21,6 +21,7 @@ Install via RubyGems:
21
21
 
22
22
  Using the search url http://miami.craigslist.org/search/sss?query=apple
23
23
 
24
+ require 'rubygems'
24
25
  require 'libcraigscrape'
25
26
  require 'date'
26
27
  require 'pp'
@@ -34,6 +35,7 @@ Using the search url http://miami.craigslist.org/search/sss?query=apple
34
35
 
35
36
  Under the category url http://miami.craigslist.org/apa/
36
37
 
38
+ require 'rubygems'
37
39
  require 'libcraigscrape'
38
40
  require 'pp'
39
41
 
@@ -46,6 +48,7 @@ Under the category url http://miami.craigslist.org/apa/
46
48
 
47
49
  This grabs the full details under the specific post http://miami.craigslist.org/mdc/sys/1140808860.html
48
50
 
51
+ require 'rubygems'
49
52
  require 'libcraigscrape'
50
53
 
51
54
  post = CraigScrape.scrape_full_post 'http://miami.craigslist.org/mdc/sys/1140808860.html'
@@ -55,6 +58,7 @@ This grabs the full details under the specific post http://miami.craigslist.org/
55
58
 
56
59
  This grabs the post summaries of the single listings at http://miami.craigslist.org/search/sss?query=laptop
57
60
 
61
+ require 'rubygems'
58
62
  require 'libcraigscrape'
59
63
 
60
64
  listing = CraigScrape.scrape_listing 'http://miami.craigslist.org/search/sss?query=laptop'
data/Rakefile CHANGED
@@ -4,16 +4,19 @@ require 'rake/gempackagetask'
4
4
  require 'rake/rdoctask'
5
5
  require 'rake/testtask'
6
6
  require 'fileutils'
7
+ require 'tempfile'
8
+
7
9
  include FileUtils
8
10
 
9
11
  RbConfig = Config unless defined? RbConfig
10
12
 
11
13
  NAME = "libcraigscrape"
12
- VERS = ENV['VERSION'] || "0.5"
14
+ VERS = ENV['VERSION'] || "0.6"
13
15
  PKG = "#{NAME}-#{VERS}"
14
16
 
15
17
  RDOC_OPTS = ['--quiet', '--title', 'The libcraigscrape Reference', '--main', 'README', '--inline-source']
16
- PKG_FILES = %w(CHANGELOG COPYING COPYING.LESSER Rakefile) + Dir.glob("{bin,test,lib}/**/*")
18
+ RDOC_FILES = ['README', "CHANGELOG", "COPYING","COPYING.LESSER", 'bin/craigwatch']
19
+ PKG_FILES = (%w(Rakefile) + RDOC_FILES + Dir.glob("{bin,test,lib}/**/*")).uniq
17
20
 
18
21
  SPEC =
19
22
  Gem::Specification.new do |s|
@@ -24,7 +27,7 @@ SPEC =
24
27
  s.bindir = 'bin'
25
28
  s.executables = 'craigwatch'
26
29
  s.rdoc_options += RDOC_OPTS
27
- s.extra_rdoc_files = ['README', "CHANGELOG", "COPYING","COPYING.LESSER", 'bin/craigwatch']
30
+ s.extra_rdoc_files = RDOC_FILES
28
31
  s.summary = "quick, easy, craigslist parsing library that takes the monotony out of working with craigslist posts and listings"
29
32
  s.description = s.summary
30
33
  s.author = "Chris DeRose, DeRose Technologies, Inc."
@@ -50,7 +53,7 @@ Rake::RDocTask.new do |rdoc|
50
53
  rdoc.rdoc_dir = 'doc/rdoc'
51
54
  rdoc.options += RDOC_OPTS
52
55
  rdoc.main = "README"
53
- rdoc.rdoc_files.add ['README', 'CHANGELOG', 'COPYING', 'COPYING.LESSER', 'bin/craigwatch','lib/**/*.rb']
56
+ rdoc.rdoc_files.add RDOC_FILES+['lib/**/*.rb']
54
57
  end
55
58
 
56
59
  Rake::GemPackageTask.new(SPEC) do |p|
@@ -70,3 +73,36 @@ end
70
73
  task :uninstall => [:clean] do
71
74
  sh %{sudo gem uninstall #{NAME}}
72
75
  end
76
+
77
+ task :pkg_archives do
78
+ base_dir = File.dirname __FILE__
79
+ package_name = '%s-%s' % [NAME,VERS]
80
+ packages_base = "#{base_dir}/pkg"
81
+ packaging_dir = '%s/%s' % [ packages_base,package_name ]
82
+
83
+ begin
84
+ # First we create a proper package-X.X directory:
85
+ PKG_FILES.each do |p_f|
86
+ base_file = '%s/%s' % [base_dir, p_f]
87
+ packaged_file = '%s/%s' % [packaging_dir, p_f]
88
+ packaged_file_dirname = File.dirname packaged_file
89
+
90
+ # We really don't care to do anything about these - we'll recreate it when/if its needed
91
+ next if File.directory? base_file
92
+
93
+ FileUtils.mkdir_p packaged_file_dirname unless File.directory? packaged_file_dirname
94
+
95
+ FileUtils.cp base_file, packaged_file unless File.exists? packaged_file
96
+ end
97
+
98
+ # Remove any old archives we'd be replacing:
99
+ %w(zip tar.bz2).each{ |ext| FileUtils.rm "#{packaging_dir}.#{ext}" if File.exist? "#{packaging_dir}.#{ext}" }
100
+
101
+ # Now let's create some archives:
102
+ sh %{cd #{packages_base} && tar -cjvf #{package_name}.tar.bz2 #{package_name}}
103
+ sh %{cd #{packages_base} && zip -r #{package_name}.zip #{package_name}}
104
+ ensure
105
+ # Delete that temp directory we created at the start here
106
+ FileUtils.rmtree packaging_dir
107
+ end
108
+ end
data/bin/craigwatch CHANGED
@@ -320,9 +320,11 @@ ActiveRecord::Schema.define do
320
320
  end
321
321
  end
322
322
 
323
- # Now let's run a report:
323
+ # We'll need these outside this next loop:
324
324
  report_summaries = []
325
+ newly_tracked_posts = []
325
326
 
327
+ # Now let's run a report:
326
328
  craig_report.each_search do |search|
327
329
 
328
330
  # Load our tracking info
@@ -333,35 +335,43 @@ craig_report.each_search do |search|
333
335
 
334
336
  last_tracked_at = (search_track.last_tracked_at) ? search_track.last_tracked_at : search.starting_at
335
337
 
338
+ already_tracked_urls = search_track.tracked_posts.collect{|tp| tp.url}
339
+
336
340
  # Let's collect all the summaries that could apply:
337
341
  new_summaries = {}
338
342
  search.listing.each do |listing|
339
- CraigScrape.scrape_posts_since(listing, last_tracked_at).each do |p_s|
340
- new_summaries[p_s.full_url] = p_s unless new_summaries.has_key? p_s.full_url or search_track.already_tracked? p_s.full_url
343
+ CraigScrape.scrape_until(listing){|p| p.date <= last_tracked_at or already_tracked_urls.include? p.full_url }.each do |p_s|
344
+ new_summaries[p_s.full_url] = p_s unless new_summaries.has_key? p_s.full_url
341
345
  end
342
346
  end
343
-
347
+
344
348
  # Let's flatten the unique'd hash into a more useable array:
345
349
  new_summaries = new_summaries.values.sort{|a,b| a.date <=> b.date} # oldest goes to bottom
346
350
 
347
- # Reject anything which doesn't match the has/has_no :
351
+ # Let's tag all the newest tracked posts that should go into the database:
352
+ # NOTE: Since all the dates are at_begining_of_day, we'll effectively have a chunk of dates tied for latest
353
+ new_summaries.reject{|p| p.date < new_summaries.last.date}.each do |p_s|
354
+ newly_tracked_posts << search_track.tracked_posts.build( :url => p_s.full_url, :created_at => p_s.date)
355
+ end
356
+
357
+ # Reject anything from this report which doesn't match the has/has_no :
348
358
  new_summaries.reject!{|s| !search.passes_filter? s }
349
359
 
350
360
  # Now Let's manage the tracking database:
351
361
  if new_summaries.length > 0
352
- # Insert all the relevant, newest tracked posts:
353
- # NOTE: Since all the dates are at_begining_of_day, we'll effectively have a chunk of dates tied for latest
354
- new_summaries.reject{|p| p.date < new_summaries.last.date}.each do |p_s|
355
- search_track.tracked_posts.create! :url => p_s.full_url, :created_at => p_s.date
356
- end
357
-
358
- # Remove all tracked posts older then we could need:
359
- TrackedPost.delete_all ['tracked_search_id = ? AND created_at < ?', search_track.id, new_summaries.last.date ]
362
+
363
+ # We'll use this in the cleanup at the bottom:
364
+ latest_post_date = new_summaries.last.date
360
365
 
361
366
  new_summaries.reverse! if search.newest_first?
362
367
 
363
368
  # We'll want to email these...
364
- report_summaries << { :postings => new_summaries, :search => search }
369
+ report_summaries << {
370
+ :postings => new_summaries,
371
+ :search => search,
372
+ :search_track => search_track,
373
+ :latest_post_date => latest_post_date
374
+ }
365
375
  end
366
376
  end
367
377
 
@@ -372,3 +382,12 @@ ReportMailer.deliver_report(
372
382
  craig_report.report_name,
373
383
  {:summaries => report_summaries, :definition => craig_report}
374
384
  ) if report_summaries.length > 0
385
+
386
+ # Save the newly created posts:
387
+ newly_tracked_posts.each{|tp| tp.save!}
388
+
389
+ # Now that we know the user has been informed, Let's commit all our database changes and end this scrape 'transaction':
390
+ report_summaries.each do |s|
391
+ # Let's do some light cleanup to keep the database size down, by removing all the old posts we're no longer tracking:
392
+ TrackedPost.delete_all [ 'tracked_search_id = ? AND created_at < ?', s[:search_track].id, s[:latest_post_date] ]
393
+ end
@@ -13,6 +13,12 @@ require 'activesupport'
13
13
  class CraigScrape
14
14
  cattr_accessor :logger
15
15
  cattr_accessor :time_now
16
+ cattr_accessor :retries_on_fetch_fail
17
+ cattr_accessor :sleep_between_fetch_retries
18
+
19
+ # Set some defaults:
20
+ self.retries_on_fetch_fail = 4
21
+ self.sleep_between_fetch_retries = 4
16
22
 
17
23
  def self.most_recently_expired_time(month, day) #:nodoc:
18
24
  now = (time_now) ? time_now : Time.now
@@ -30,6 +36,12 @@ class CraigScrape
30
36
  end
31
37
  end
32
38
 
39
+ class ParseError < StandardError #:nodoc:
40
+ end
41
+
42
+ class FetchError < StandardError #:nodoc:
43
+ end
44
+
33
45
  # PostFull represents a fully downloaded, and parsed, Craigslist post.
34
46
  # This class is generally returned by the listing scrape methods, and
35
47
  # contains the post summaries for a specific search url, or a general listing category
@@ -68,14 +80,18 @@ class CraigScrape
68
80
  POSTING_ID = /PostingID\:[ ]+([\d]+)/
69
81
  REPLY_TO = /(.+)/
70
82
  PRICE = /\$([\d]+(?:\.[\d]{2})?)/
83
+ HTML_TAG = /<\/?[^>]*>/
71
84
 
72
85
  def initialize(page) #:nodoc:
73
86
  # We proceed from easy to difficult:
74
87
 
75
88
  @images = []
76
89
 
77
- @header = he_decode page.at('h2').inner_html if page.at('h2')
78
- @title = he_decode page.at('title').inner_html if page.at('title')
90
+ h2 = page.at('h2')
91
+ @header = he_decode h2.inner_html if h2
92
+
93
+ title = page.at('title')
94
+ @title = he_decode title.inner_html if title
79
95
  @title = nil if @title.length ==0
80
96
 
81
97
  @full_section = []
@@ -99,7 +115,7 @@ class CraigScrape
99
115
  @posting_id = $1.to_i if $1
100
116
 
101
117
  # OK - so the biggest problem parsing the contents of a craigslist post is that users post invalid html all over the place
102
- # THis bad html trips up hpricot, and I've resoorted to splitting the page up using string parsing.
118
+ # This bad html trips up hpricot, and I've resorted to splitting the page up using string parsing like so:
103
119
  userbody_as_s,craigbody_as_s = $1, $2 if /\<div id\=\"userbody\">(.+)\<br[ ]*[\/]?\>\<br[ ]*[\/]?\>(.+)\<\/div\>/m.match page.to_s
104
120
 
105
121
  # Contents:
@@ -108,17 +124,23 @@ class CraigScrape
108
124
  # I made this a separate method since we're not actually parsing everything in here as-is.
109
125
  # This will make it easier for the next guy to work with if wants to parse out the information we're disgarding...
110
126
  parse_craig_body Hpricot.parse(craigbody_as_s) if craigbody_as_s
127
+
128
+ # Validate that required fields are present:
129
+ raise ParseError, "Unable to parse PostFull: %s" % page.to_html if !flagged_for_removal? and [
130
+ @contents,@posting_id,@post_time,@header,@title,@full_section
131
+ ].any?{|f| f.nil? or (f.respond_to? :length and f.length == 0)}
111
132
  end
112
133
 
134
+ # I left this here as a stub, since someone may want to parse more then what I'm currently scraping from this part of the page
113
135
  def parse_craig_body(craigbody_els) #:nodoc:
114
136
  # Location (when explicitly defined):
115
- cursor = craigbody_els.at('ul')
116
- cursor = cursor.at('li') if cursor
117
- @location = he_decode($1) if cursor and LOCATION.match(cursor.inner_html)
137
+ cursor = craigbody_els.at 'ul'
138
+ cursor = cursor.at 'li' if cursor
139
+ @location = he_decode $1 if cursor and LOCATION.match cursor.inner_html
118
140
 
119
141
  # Real estate listings can work a little different for location:
120
142
  unless @location
121
- cursor = craigbody_els.at('small')
143
+ cursor = craigbody_els.at 'small'
122
144
  cursor = cursor.previous_node until cursor.nil? or cursor.text?
123
145
 
124
146
  @location = he_decode(cursor.to_s.strip) if cursor
@@ -130,6 +152,14 @@ class CraigScrape
130
152
  @images = (img_table / 'img').collect{|i| i[:src]} if img_table
131
153
  end
132
154
 
155
+ # Returns true if this Post was parsed, and merely a 'Flagged for Removal' page
156
+ def flagged_for_removal?
157
+ (
158
+ [@contents,@posting_id,@post_time,@title].all?{|f| f.nil?} and
159
+ @header.gsub(HTML_TAG, "") == "This posting has been flagged for removal"
160
+ )
161
+ end
162
+
133
163
  # Returns the price (as float) of the item, as best ascertained by the post header
134
164
  def price
135
165
  $1.to_f if @title and @header and PRICE.match(@header.gsub(/#{@title}/, ''))
@@ -137,7 +167,7 @@ class CraigScrape
137
167
 
138
168
  # Returns the post contents with all html tags removed
139
169
  def contents_as_plain
140
- @contents.gsub(/<\/?[^>]*>/, "") if @contents
170
+ @contents.gsub HTML_TAG, "" if @contents
141
171
  end
142
172
  end
143
173
 
@@ -175,6 +205,9 @@ class CraigScrape
175
205
 
176
206
  # This will find the link on 'search listing' pages (if there is one):
177
207
  @next_page_href = next_link[:href] if next_link
208
+
209
+ # Validate that required fields are present:
210
+ raise ParseError, "Unable to parse Listings: %s" % page.to_html unless @posts.length > 0
178
211
  end
179
212
 
180
213
  end
@@ -213,8 +246,8 @@ class CraigScrape
213
246
  location_tag = p_element.at 'font'
214
247
  has_pic_tag = p_element.at 'span'
215
248
 
216
- @location = he_decode p_element.at('font').inner_html if location_tag
217
- @location = $1 if LOCATION.match @location
249
+ location = he_decode p_element.at('font').inner_html if location_tag
250
+ @location = $1 if location and LOCATION.match location
218
251
 
219
252
  @img_types = []
220
253
  if has_pic_tag
@@ -231,11 +264,17 @@ class CraigScrape
231
264
  @date = CraigScrape.most_recently_expired_time $1, $2.to_i
232
265
  end
233
266
 
234
- @label = he_decode title_anchor.inner_html
235
- @label = $1 if LABEL.match @label
236
-
237
- @href = title_anchor[:href]
267
+ if title_anchor
268
+ label = he_decode title_anchor.inner_html
269
+ @label = $1 if LABEL.match label
270
+
271
+ @href = title_anchor[:href]
272
+ end
273
+
238
274
  @base_url = base_url
275
+
276
+ # Validate that required fields are present:
277
+ raise ParseError, "Unable to parse PostSummary: %s" % p_element.to_html if [@label,@href].any?{|f| f.nil? or f.length == 0}
239
278
  end
240
279
 
241
280
  # Returns the full uri including host and scheme, not just the href
@@ -275,7 +314,13 @@ class CraigScrape
275
314
  def self.scrape_listing(listing_url)
276
315
  current_uri = ( listing_url.class == String ) ? URI.parse(listing_url) : listing_url
277
316
 
278
- CraigScrape::Listings.new Hpricot.parse(self.fetch_url(current_uri)), '%s://%s' % [current_uri.scheme, current_uri.host]
317
+ uri_contents = self.fetch_url(current_uri)
318
+
319
+ CraigScrape::Listings.new Hpricot.parse(uri_contents), '%s://%s' % [current_uri.scheme, current_uri.host]
320
+
321
+ rescue ParseError
322
+ puts "Encountered error here! : #{uri_contents.inspect}"
323
+ exit
279
324
  end
280
325
 
281
326
  # Continually scrapes listings, using the supplied url as a starting point, until the supplied block returns true or
@@ -323,17 +368,40 @@ class CraigScrape
323
368
  end
324
369
 
325
370
  def self.fetch_url(uri) #:nodoc:
326
- # This handles the redirects for us
327
- uri_dest = ( uri.class == String ) ? URI.parse(uri) : uri
328
-
329
- logger.info "Requesting: %s" % uri_dest.to_s unless logger.nil?
371
+ fetch_attempts = 0
330
372
 
331
- resp, data = Net::HTTP.new( uri_dest.host, uri_dest.port).get uri_dest.request_uri, nil
373
+ begin
374
+ # This handles the redirects for us
375
+ uri_dest = ( uri.class == String ) ? URI.parse(uri) : uri
376
+
377
+ logger.info "Requesting: %s" % uri_dest.to_s if logger
378
+
379
+ resp, data = Net::HTTP.new( uri_dest.host, uri_dest.port).get uri_dest.request_uri, nil
380
+
381
+ if resp.response.code == "200"
382
+ data
383
+ elsif resp.response['Location']
384
+ redirect_to = resp.response['Location']
385
+ self.fetch_url(redirect_to)
386
+ else
387
+ # Sometimes Craigslist seems to return 404's for no good reason, and a subsequent fetch will give you what you want
388
+ error_description = 'Unable to fetch "%s" (%s)' % [ uri_dest.to_s, resp.response.code ]
332
389
 
333
- redirect_to = resp.response['Location'] if resp.response['Location']
334
- redirect_to = $1 if /<META HTTP-EQUIV=\"REFRESH\" CONTENT=\"0;[ ]*URL=(.*)\">/i.match data
390
+ logger.info error_description if logger
391
+
392
+ raise FetchError, error_description
393
+ end
394
+ rescue FetchError => err
395
+ fetch_attempts += 1
396
+
397
+ if retries_on_fetch_fail <= CraigScrape.retries_on_fetch_fail
398
+ sleep CraigScrape.sleep_between_fetch_retries if CraigScrape.sleep_between_fetch_retries
399
+ retry
400
+ else
401
+ raise err
402
+ end
403
+ end
335
404
 
336
- (redirect_to) ? self.fetch_url(redirect_to) : data
337
405
  end
338
406
 
339
407
  def self.uri_from_href(base_uri, href) #:nodoc:
@@ -0,0 +1,226 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2
+ <html><head>
3
+ <title>south florida furniture - all classifieds - craigslist</title>
4
+
5
+ <meta name="description" content="craigslist furniture - all classifieds for south florida ">
6
+ <meta name="keywords" content="south florida furniture - all craigslist, classifieds, want ads ">
7
+
8
+
9
+
10
+ <link rel=alternate type="application/rss+xml" href="index.rss" title="RSS feed for craigslist | furniture - all in south florida ">
11
+ <link rel="stylesheet" title="craigslist" href="http://www.craigslist.org/styles/craigslist.css" type="text/css" media="all">
12
+ </head>
13
+
14
+ <body class="toc">
15
+
16
+ <a name="top"></a>
17
+
18
+ <div class="bchead"><span id="ef">
19
+
20
+ [ <a href="http://www.craigslist.org/about/help/">help</a> ]
21
+ [ <a href="https://post.craigslist.org/mia/S">post</a> ]</span>
22
+
23
+ <a href="/"> south florida craigslist</a> &gt; <a href="/fua/">furniture - all</a><div id="satabs"> <b>all south florida</b> <a href="/mdc/fua/">miami / dade</a> <a href="/brw/fua/">broward county</a> <a href="/pbc/fua/">palm beach co</a> </div>
24
+ </div>
25
+
26
+ <blockquote>
27
+ <form action="/search/fua" method="get" onsubmit="ckCAbb();">
28
+
29
+ <script type="text/javascript"><!--
30
+ function ckCAbb() {
31
+ t = document.getElementById("cAbb");
32
+ if (t.value == "fua") { t.disabled = true; }
33
+ }
34
+ -->
35
+ </script>
36
+
37
+ <table width="95%" cellpadding="2" style="white-space: nowrap; background:#eee; border:1px solid gray;" summary="">
38
+ <tr>
39
+ <td align="right" width="1">search for:</td>
40
+ <td width="30%"><input id="query" name="query" size="30" value=""> in:
41
+ <select id="cAbb" name="catAbbreviation">
42
+ <option value="ccc">all community<option value="eee">all event<option value="sss">all for sale / wanted<option disabled value="">--<option value="art"> art &amp; crafts
43
+ <option value="pts"> auto parts
44
+ <option value="bab"> baby &amp; kid stuff
45
+ <option value="bar"> barter
46
+ <option value="bik"> bicycles
47
+ <option value="boa"> boats
48
+ <option value="bks"> books
49
+ <option value="bfs"> business
50
+ <option value="cta"> cars &amp; trucks - all
51
+ <option value="ctd"> cars &amp; trucks - by dealer
52
+ <option value="cto"> cars &amp; trucks - by owner
53
+ <option value="emd"> cds / dvds / vhs
54
+ <option value="clo"> clothing
55
+ <option value="clt"> collectibles
56
+ <option value="sys"> computers &amp; tech
57
+ <option value="ele"> electronics
58
+ <option value="grd"> farm &amp; garden
59
+ <option value="zip"> free stuff
60
+ <option value="fua" selected> furniture - all
61
+ <option value="fud"> furniture - by dealer
62
+ <option value="fuo"> furniture - by owner
63
+ <option value="tag"> games &amp; toys
64
+ <option value="gms"> garage sales
65
+ <option value="for"> general
66
+ <option value="hsh"> household
67
+ <option value="wan"> items wanted
68
+ <option value="jwl"> jewelry
69
+ <option value="mat"> materials
70
+ <option value="mcy"> motorcycles/scooters
71
+ <option value="msg"> musical instruments
72
+ <option value="pho"> photo/video
73
+ <option value="rvs"> recreational vehicles
74
+ <option value="spo"> sporting goods
75
+ <option value="tix"> tickets
76
+ <option value="tls"> tools
77
+ <option disabled value="">--<option value="ggg">all gigs<option value="hhh">all housing<option value="jjj">all jobs<option value="ppp">all personals<option value="res">all resume<option value="bbb">all services offered</select>
78
+ <input type="submit" value="Search">
79
+ </td><td>
80
+ <label><input type="checkbox" name="srchType" value="T"
81
+ title="check this box to search only posting titles"> only search titles</label>
82
+ </td>
83
+ </tr>
84
+
85
+ <tr>
86
+ <td align="right" width="1">price:</td>
87
+ <td><input name="minAsk" size="6" value="min" onfocus="value=''">&nbsp;<input name="maxAsk" size="6" value="max" onfocus="value=''">&nbsp;</td>
88
+ <td align="left"><label><input type="checkbox" name="hasPic" value="1"> has image</label></td>
89
+ </tr></table></form></blockquote><span id="showPics"></span><span id="hidePics"></span>
90
+
91
+ <blockquote>
92
+ <table width="95%" summary="">
93
+ <tr>
94
+ <td valign="top">[ Thu, 21 May 19:27:50 ]</td>
95
+ <td valign="top" id="messages"><span class="hl"> [ <a href="/about/prohibited.items">partial list of prohibited items</a> ] </span> <span class="hl"> [ <a href="http://www.recalls.gov/">avoid recalled items</a> ] </span> <span class="hl"> [ <b><a href="/about/safety">PERSONAL SAFETY TIPS</a></b> ] </span> <span class="hl"> [ <b><a href="/about/scams">AVOIDING SCAMS &amp; FRAUD</a></b> ] </span> <span class="hl"> [<a href="/cgi-bin/success.stories.cgi">success story?</a>]</span> </td>
96
+ </tr>
97
+ </table>
98
+
99
+ <h4>Fri May 15</h4>
100
+ <p><a href="/brw/fuo/1171713452.html">Wood Office Shelf - $250 -</a><font size="-1"> (Coconut Creek)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
101
+ <p><a href="/mdc/fuo/1171708187.html">_+_+_+_ $ 35 _+_+_+_ -</a><font size="-1"> (***** Miami Beach *****)</font> <span class="p"> img</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
102
+ <p><a href="/brw/fuo/1171707696.html">Leather sofa - damage on one arm - $100 -</a><font size="-1"> (Margate, FL)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
103
+ <p><a href="/brw/fuo/1171705893.html">Keelan 2PC Sofa Set -</a><font size="-1"> (Davie)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
104
+ <p><a href="/brw/fuo/1171703787.html">Wood TV stand and end table - $70 -</a><font size="-1"> (Margate, FL)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
105
+ <p><a href="/brw/fuo/1171703282.html">Sofabed(queen), love seat, 2 tables - $500 -</a><font size="-1"> (Broward)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
106
+ <p><a href="/brw/fuo/1171697270.html">Glass top coffee table and end table - $60 -</a><font size="-1"> (Margate, FL)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
107
+ <h4></h4>
108
+ <p><a href="/brw/fuo/1171694683.html">Bassett Sofa and Chair and a half - $350 -</a><font size="-1"> (Margate, FL)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
109
+ <h4>Fri May 15</h4>
110
+ <p><a href="/brw/fuo/1171689641.html">KING SIZE WATER BED -</a><font size="-1"> (Oakland Park)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
111
+ <p><a href="/mdc/fuo/1171689432.html">Big Red Bed Couch with Cover -</a><font size="-1"> (Coral Gables)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
112
+ <p><a href="/brw/fuo/1171679181.html">Printer Stand - $25 -</a><font size="-1"> (Parkland)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
113
+ <p><a href="/brw/fuo/1171673408.html">Bedroom Furniture Set - $100 -</a><font size="-1"> (Parkland)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
114
+ <p><a href="/brw/fuo/1171683390.html">LIGHT RATTAN TABLE CHAIRS (4) - $40 -</a><font size="-1"> (HOLLYWOOD)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
115
+ <p><a href="/brw/fuo/1171680733.html">amazing furniture -</a><font size="-1"> (hollywood)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
116
+ <h4>Thu May 14</h4>
117
+ <p><a href="/mdc/fud/1171659425.html">BLACK AND WHITE COFFEE TABLE - $179 -</a><font size="-1"> (MIAMI)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
118
+ <p><a href="/mdc/fuo/1171655834.html">ESTATE SALE -</a><font size="-1"> (SUNNY ISLES BEACH)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
119
+ <p><a href="/brw/fuo/1171654085.html">Patio Set - round w/ 4 chairs, wrought iron, quality, scroll design - $100 -</a><font size="-1"> (East Deerfield beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
120
+ <p><a href="/mdc/fuo/1171645673.html">White Floor Lamp - $35 -</a><font size="-1"> (NMB)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
121
+ <p><a href="/mdc/fuo/1171640310.html">3 piece wall unit - $165 -</a><font size="-1"> (NMB)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
122
+ <p><a href="/pbc/fuo/1171636316.html">Black Lacquer Dining Room Set - $1750 -</a><font size="-1"> (Lake Worth)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
123
+ <p><a href="/brw/fuo/1171634083.html">Piano- Baby Grand - $4800 -</a><font size="-1"> (Hollywood, Florida)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
124
+ <p><a href="/brw/fuo/1171045933.html">Beautiful Elephant Glass Table - $190 -</a><font size="-1"> (Lighthouse Point, FL)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
125
+ <p><a href="/brw/fuo/1171620976.html">$$$$$$ *** DINING ROOM HUTCH *** dark brown / lots of room - $75 -</a><font size="-1"> (Coral Springs)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
126
+ <p><a href="/pbc/fuo/1171618625.html">BEAUTIFUL SOUTHWESTERN HUTCH - $1100 -</a><font size="-1"> (Boca Raton, Florida)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
127
+ <p><a href="/brw/fuo/1171605305.html">Extraordinary Murals &amp; Faux Finishings by Jorde!!bca -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
128
+ <p><a href="/brw/fuo/1171609292.html">armoire - $290 -</a><font size="-1"> (Fort Lauderdale)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
129
+ <p><a href="/mdc/fuo/1171608196.html">Queen Sleigh Bed - Natural Finish - $300 -</a><font size="-1"> (Kendall)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
130
+ <p><a href="/brw/fuo/1171592461.html">Safari Home Decor - $1 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
131
+ <p><a href="/brw/fuo/1171598534.html">Antique french cabinet - $890 -</a><font size="-1"> (Fort Lauderdale)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
132
+ <p><a href="/mdc/fuo/1171047868.html">bunk bed desk drawer set - $450 -</a><font size="-1"> (miami beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
133
+ <p><a href="/pbc/fuo/1171597616.html">RED microfiber sectional couch - $300 -</a><font size="-1"> (Lake Worth/Greenacres)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
134
+ <p><a href="/mdc/fuo/1171595497.html">Wood Futon - $50 -</a><font size="-1"> (Coral Gables)</font> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
135
+ <p><a href="/brw/fuo/1171595817.html">furniture for sale - $1200 -</a><font size="-1"> (ft lauderdale)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
136
+ <p><a href="/brw/fuo/1171595527.html">New King Koil King Size Bed Matress/Boxspring - $800 -</a><font size="-1"> (Plantation)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
137
+ <p><a href="/brw/fuo/1171592934.html">DINIG BUFFET AND WINERY wood beauty stylish - $590 -</a><font size="-1"> (Fort Lauderdale)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
138
+ <p><a href="/brw/fuo/1171591182.html">HUGE MOVING SALE EVERYTHING MUST GO!!!!!! -</a> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
139
+ <p><a href="/mdc/fuo/1171587077.html">Wooden/Metal Computer Desk - $30 -</a><font size="-1"> (Coral Gables)</font> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
140
+ <p><a href="/brw/fuo/1171581992.html">Safari Animals Home Decor - $1 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
141
+ <p><a href="/mdc/fud/1171582327.html">NEW AG MANAGEMENT OFFICE CHAIRS - $219 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
142
+ <p><a href="/pbc/fuo/1171581302.html">Contempoary Style Leather Sofa -</a><font size="-1"> (West Palm Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
143
+ <p><a href="/brw/fuo/1171577772.html">BREAK FAST DINING TABLE MARBLE WITH 6 CHAIRS - $140 -</a><font size="-1"> (PARKLAND/CORAL SPRING)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
144
+ <p><a href="/brw/fuo/1171577687.html">PATIO FURNITURE CAST ALUMINUM - $350 -</a><font size="-1"> (POMPANO BEACH)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
145
+ <p><a href="/mdc/fud/1164329587.html"> pool table - $250 -</a> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
146
+ <p><a href="/pbc/fuo/1171576098.html">Corner table - $5 -</a><font size="-1"> (Royal Palm Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
147
+ <p><a href="/mdc/fud/1171574897.html">PK22 Easy Leather Chair - $599 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
148
+ <p><a href="/mdc/fuo/1171076966.html">new table and chairs - $500 -</a> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
149
+ <p><a href="/pbc/fuo/1171572816.html">Elegant Floor Lamp -</a><font size="-1"> (West Palm Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
150
+ <p><a href="/brw/fuo/1171570670.html">45" Glass top dinette - $190 -</a><font size="-1"> (Coconut Creek)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
151
+ <p><a href="/brw/fuo/1171570228.html">Couch w/ Pull out Bed - $150 -</a><font size="-1"> (Pompano Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
152
+ <p><a href="/mdc/fud/1171570038.html">Eileen Gray Adjustable Tables - New -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
153
+ <p><a href="/mdc/fuo/1171568132.html">CHEAP GLASS TOP DESK - $400 -</a><font size="-1"> (kendall/163pl. 88st.)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
154
+ <p><a href="/pbc/fuo/1171567577.html">$10 couch - $10 -</a><font size="-1"> (boca raton)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
155
+ <p><a href="/brw/fuo/1171564018.html">Massage chair - $170 -</a><font size="-1"> (Pompano Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
156
+ <p><a href="/mdc/fuo/1171564859.html">Loveseat - $350 -</a><font size="-1"> (West Kendall)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
157
+ <p><a href="/pbc/fuo/1171565497.html">Ironman Weight Lifting Set - $300 -</a><font size="-1"> (wellinton)</font> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
158
+ <p><a href="/mdc/fud/1171564511.html">Eiffel Tower Base Dining Side Chairs - New - $99 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
159
+ <p><a href="/pbc/fuo/1171564283.html">Stereo Receiver w/Graphic Analyzer &amp; Speakers - $100 -</a><font size="-1"> (Boynton Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
160
+ <p><a href="/pbc/fuo/1171563499.html">&gt;&gt;&gt;&gt; BEAUTIFUL SOFA FOR SALE &lt;&lt;&lt;&lt; - $200 -</a><font size="-1"> (Palm Beach Gardens)</font> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
161
+ <p><a href="/brw/fuo/1171562707.html">ENTERTAINMENT CENTER CUSTOM DESIGN - $250 -</a><font size="-1"> (PARKLAND/CORAL SPRING)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
162
+ <p><a href="/mdc/fud/1171561987.html">NEW CELLULA STYLE 48" CHANDELIER - $349 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
163
+ <p><a href="/mdc/fuo/1171561507.html">Beautiful glass top coffee table - $50 -</a><font size="-1"> (Bay Harbor Islands)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
164
+ <p><a href="/pbc/fuo/1171561451.html">9 Drawer Solid Wood Dresser and Mirror - $200 -</a><font size="-1"> (Boynton Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
165
+ <p><a href="/mdc/fuo/1171559897.html">Bunkbed - $60 -</a><font size="-1"> (Dadeland)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
166
+ <p><a href="/pbc/fuo/1171559299.html">Crate &amp; Barrel Side Table - $65 -</a><font size="-1"> (Abacoa/Jupiter)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
167
+ <p><a href="/pbc/fuo/1171558286.html">Sectional Sofa - $400 -</a><font size="-1"> (Palm Beach Gardens)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
168
+ <p><a href="/brw/fuo/1171463677.html">GEORGEOUS CHEETAH PRINT SEALY SOFA - MUST SELL - $299 -</a><font size="-1"> (Hollywood Lakes/North)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
169
+ <p><a href="/brw/fuo/1171558038.html">DINING ROOM TABLE SET - $290 -</a><font size="-1"> (Pembroke Pines)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
170
+ <p><a href="/pbc/fuo/1171556722.html">Sterns and Foster King Size Mattress/ Box Spring - $350 -</a><font size="-1"> (Palm Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
171
+ <p><a href="/pbc/fuo/1171556629.html">dining set high end must see - $6500 -</a><font size="-1"> (boca raton)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
172
+ <p><a href="/mdc/fud/1171556148.html">New Victoria Ghost Style Transparent Side Chairs and Armchair - $185 -</a> <span class="p"> pic&nbsp;img</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
173
+ <p><a href="/mdc/fuo/1171554780.html">Modern wood/black patio table w/chairs - $75 -</a><font size="-1"> (Miami)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
174
+ <p><a href="/mdc/fud/1171553514.html">Leather Matteo Adjustable Barstools By Nuevo - $367 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
175
+ <p><a href="/pbc/fuo/1171550178.html">Modern Glass TV/Audio/Video Stand Entertainment Center Only6months old - $350 -</a><font size="-1"> (WPB/Wellington/Boynton Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
176
+ <p><a href="/mdc/fuo/1171549884.html">Patio Wood square table+4 director chairs+umbrela - $179 -</a><font size="-1"> (Aventura)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
177
+ <p><a href="/mdc/fud/1171548703.html">New Wagner Wishbone "Y" Dining Chairs - $249 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
178
+ <p><a href="/mdc/fuo/1171545409.html">Super sale!! Patio Light fiji bamboo set - $155 -</a><font size="-1"> (N.Dade)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
179
+ <p><a href="/mdc/fud/1171545312.html">Leather Springing Lounge Chair By Poul Kjaerhol - $499 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
180
+ <p><a href="/mdc/fud/1171543887.html">New Amici Stainless Steel Benches in Brused or Polished finish - $630 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
181
+ <p><a href="/mdc/fuo/1171543491.html">High wood table &amp; 2 chairs patio/outdoor/Indoor - $80 -</a><font size="-1"> (Miami)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
182
+ <p><a href="/brw/fuo/1171543172.html">Adjustable Metal Bed Frame - $40 -</a><font size="-1"> (Deerfield Beach)</font> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
183
+ <p><a href="/mdc/fud/1171542823.html">Aluminum Group Padded Management Chairs - 4 colors - $245 -</a> <span class="p"> pic&nbsp;img</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
184
+ <p><a href="/brw/fuo/1171542223.html">Leather Couch, Seat, &amp; Ottoman - $50 -</a><font size="-1"> (Pembroke Pines)</font> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
185
+ <p><a href="/brw/fuo/1171539653.html">great designer chair - $40 -</a><font size="-1"> (deerfield beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
186
+ <p><a href="/mdc/fuo/1171541074.html">Dark bamboo set for the patio - $270 -</a><font size="-1"> (Miami)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
187
+ <p><a href="/mdc/fud/1171539959.html">Eames Style Lounge Chair and Ottoman - Black,Vanilla - $895 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
188
+ <p><a href="/brw/fuo/1171534920.html">***** ULTRA MODERN OTTOMAN - DARK CHOCOLATE ***** - $129 -</a><font size="-1"> (Miami / Kendall)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
189
+ <p><a href="/mdc/fud/1171538677.html">NEW JAPANESE STYLE PLATFORM BED - King or Queen Sizes - $1375 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
190
+ <p><a href="/mdc/fuo/1171519822.html">***** MODERN DARK BROWN OTTOMAN ***** - $125 -</a><font size="-1"> (Miami / Kendall area)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
191
+ <p><a href="/mdc/fud/1171536546.html">New Alexander Barstools - Lem Style - 5colors - $249 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
192
+ <p><a href="/mdc/fuo/1171536378.html">Super patio/Outdoor sectional 6 piece - $475 -</a><font size="-1"> (North Dade)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
193
+ <p><a href="/pbc/fuo/1171536353.html">&gt; 0 &lt; Pittsburgh Steelers Beer Pong Table For Sale!Super Bowl Champs!! - $120 -</a><font size="-1"> (Tequesta/Jupiter)</font> <span class="p"> pic&nbsp;img</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
194
+ <p><a href="/mdc/fuo/1171535233.html">American Signature Loveseat - $250 -</a><font size="-1"> (Bay Harbor Islands)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
195
+ <p><a href="/mdc/fud/1171535495.html">New Le Corbusier Pony Lounge Chair - $899 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
196
+ <p><a href="/pbc/fuo/1171458403.html">Broyhill Wood Coffee Table with Drawers - $50 -</a><font size="-1"> (West Lake Worth)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
197
+ <p><a href="/pbc/fuo/1171534833.html">Queen Anne Traditional End Tables Stiffel Brass Lamps - $70 -</a><font size="-1"> (Deerfield Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
198
+ <p><a href="/mdc/fud/1171533476.html">Eames Style Molded plywood Dining Chairs w/Megtal Legs - $199 -</a> <span class="p"> pic&nbsp;img</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
199
+ <p><a href="/mdc/fuo/1171526415.html">Armoire/Antique finished - $1900 -</a><font size="-1"> (Coral Gables, FL)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
200
+ <p><a href="/mdc/fud/1171532234.html">NEW MEGAN LEATHER ITALIAN STYLE DINING CHAIRS - 2 colors - $150 -</a> <span class="p"> pic</span> &lt;&lt;<i><a href="/fud/">furniture&nbsp;by dealer</a></i></p>
201
+ <p><a href="/pbc/fuo/1171531145.html">(SOLD) Miami Hurricanes Beer Pong Table!! Buy yours custom!! - $175 -</a><font size="-1"> (Jupiter/ Tequesta)</font> <span class="p"> pic&nbsp;img</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
202
+ <p><a href="/brw/fuo/1171530985.html">China Cabinet, Table and chairs REDUCED - $100 -</a><font size="-1"> (Deerfield Beach)</font> <span class="p"> pic</span> &lt;&lt;<i><a href="/fuo/">furniture&nbsp;by owner</a></i></p>
203
+
204
+
205
+ <p align="center"><font size="4"><a href="index9000.html">next 100 postings</a></font>
206
+
207
+ <div id="footer">
208
+ <hr>
209
+ <span id="copy">
210
+ Copyright &copy; 2009 craigslist, inc.<br>
211
+ <a href="#top">Back to top of page</a>
212
+ </span>
213
+ <span class="rss">
214
+ <a class="l" href="http://miami.craigslist.org/fua/index.rss">RSS</a>
215
+ <a href="http://www.craigslist.org/about/rss">(?)</a><br>
216
+ <a class="y" href="http://add.my.yahoo.com/rss?url=http://miami.craigslist.org/fua/index.rss">add to My Yahoo!</a>
217
+ </span>
218
+ </div>
219
+ <br><br>
220
+
221
+ <div id="floater">&nbsp;</div>
222
+
223
+ </blockquote>
224
+ <script type="text/javascript" src="http://www.craigslist.org/js/jquery.js"></script>
225
+ </body>
226
+ </html>
@@ -5,14 +5,12 @@ require 'test/unit'
5
5
 
6
6
  class CraigslistListingTest < Test::Unit::TestCase
7
7
 
8
- def test_no_puke
8
+ def test_pukes
9
9
  google = read_as_hpricot('google.html')
10
-
11
- assert_nothing_raised{ CraigScrape::PostSummary.new google}
12
-
13
- assert_nothing_raised{ CraigScrape::Listings.new google }
14
-
15
- assert_nothing_raised{ CraigScrape::PostFull.new google }
10
+
11
+ assert_raise(CraigScrape::ParseError){ CraigScrape::PostSummary.new google }
12
+ assert_raise(CraigScrape::ParseError){ CraigScrape::Listings.new google }
13
+ assert_raise(CraigScrape::ParseError){ CraigScrape::PostFull.new google }
16
14
  end
17
15
 
18
16
  def test_listing_parse
@@ -141,6 +139,18 @@ EOD
141
139
  short_search = CraigScrape::Listings.new read_as_hpricot('listing_samples/short_search_output.html')
142
140
  assert_equal nil, short_search.next_page_href
143
141
  assert_equal 93, short_search.posts.length
142
+
143
+ mia_fua_index8900_052109 = CraigScrape::Listings.new read_as_hpricot('listing_samples/mia_fua_index8900.5.21.09.html')
144
+ assert_equal 'index9000.html', mia_fua_index8900_052109.next_page_href
145
+ assert_equal 100, mia_fua_index8900_052109.posts.length
146
+ mia_fua_index8900_052109.posts[0..13].each do |l|
147
+ assert_equal 5, l.date.month
148
+ assert_equal 15, l.date.day
149
+ end
150
+ mia_fua_index8900_052109.posts[14..99].each do |l|
151
+ assert_equal 5, l.date.month
152
+ assert_equal 14, l.date.day
153
+ end
144
154
  end
145
155
 
146
156
  def test_posting_parse
@@ -211,6 +221,7 @@ EOD
211
221
  assert_equal 225000.0, posting4.price
212
222
 
213
223
  posting5 = CraigScrape::PostFull.new read_as_hpricot('post_samples/posting5.html')
224
+ assert_equal true, posting5.flagged_for_removal?
214
225
  assert_equal nil, posting5.contents
215
226
  assert_equal ["south florida craigslist", "palm beach co", "apts/housing for rent"], posting5.full_section
216
227
  assert_equal "This posting has been <a href=\"http://www.craigslist.org/about/help/flags_and_community_moderation\">flagged</a> for removal", posting5.header
@@ -221,7 +232,7 @@ EOD
221
232
  assert_equal nil, posting5.post_time
222
233
  assert_equal [], posting5.images
223
234
  assert_equal nil, posting5.contents_as_plain
224
- assert_equal nil, posting5.price
235
+ assert_equal nil, posting5.price
225
236
  end
226
237
 
227
238
  private
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libcraigscrape
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.5"
4
+ version: "0.6"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris DeRose, DeRose Technologies, Inc.
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-05 00:00:00 -04:00
12
+ date: 2009-05-21 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -55,18 +55,20 @@ extra_rdoc_files:
55
55
  - COPYING.LESSER
56
56
  - bin/craigwatch
57
57
  files:
58
+ - Rakefile
59
+ - README
58
60
  - CHANGELOG
59
61
  - COPYING
60
62
  - COPYING.LESSER
61
- - Rakefile
63
+ - bin/craigwatch
62
64
  - bin/craig_report_schema.yml
63
65
  - bin/report_mailer
64
66
  - bin/report_mailer/craigslist_report.html.erb
65
67
  - bin/report_mailer/craigslist_report.plain.erb
66
- - bin/craigwatch
67
68
  - test/listing_samples
68
69
  - test/listing_samples/category_output.html
69
70
  - test/listing_samples/short_search_output.html
71
+ - test/listing_samples/mia_fua_index8900.5.21.09.html
70
72
  - test/listing_samples/category_output_2.html
71
73
  - test/listing_samples/long_search_output.html
72
74
  - test/test_craigslist_listing.rb
@@ -79,7 +81,6 @@ files:
79
81
  - test/post_samples/posting2.html
80
82
  - test/google.html
81
83
  - lib/libcraigscrape.rb
82
- - README
83
84
  has_rdoc: true
84
85
  homepage: http://www.derosetechnologies.com/community/libcraigscrape
85
86
  post_install_message: