We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
MI failing since at least 2018-05-12
MI has been failing since 2018-05-12

Based on automated runs it appears that MI has not run successfully in 2 days (2018-05-12).


```
  02:47:18 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2018-HJR-GG
02:47:19 INFO pupa: save bill HJR GG in 2017-2018 as bill_dcf34e60-5681-11e8-a8aa-029b97b45e2a.json
02:47:19 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2017-HJR-HH
02:47:20 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2018-HJR-HH
02:47:21 INFO pupa: save bill HJR HH in 2017-2018 as bill_de254248-5681-11e8-a8aa-029b97b45e2a.json
02:47:21 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2017-HJR-II
02:47:22 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2018-HJR-II
02:47:23 INFO pupa: save bill HJR II in 2017-2018 as bill_df57e738-5681-11e8-a8aa-029b97b45e2a.json
02:47:23 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2017-HJR-JJ
02:47:24 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2018-HJR-JJ
02:47:25 WARNING pupa: Cannot open bill page for HJR JJ; skipping
02:47:25 INFO scrapelib: GET - http://www.senate.michigan.gov/senatorinfo_list.html
02:47:25 INFO scrapelib: HEAD - http://www.senate.michigan.gov/_images/Booher.png
02:47:26 INFO scrapelib: GET - http://www.SenatorDarwinBooher.com/contact/
02:47:27 INFO pupa: save person Booher, Darwin L as person_e21c0558-5681-11e8-a8aa-029b97b45e2a.json
loaded Open States pupa settings...
mi (scrape, import)
  bills: {}
  people: {}
  committees: {}
Traceback (most recent call last):
  File "/opt/openstates/venv-pupa//bin/pupa", line 11, in <module>
    load_entry_point('pupa', 'console_scripts', 'pupa')()
  File "/opt/openstates/venv-pupa/src/pupa/pupa/cli/__main__.py", line 68, in main
    subcommands[args.subcommand].handle(args, other)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py", line 260, in handle
    return self.do_handle(args, other, juris)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py", line 305, in do_handle
    report['scrape'] = self.do_scrape(juris, args, scrapers)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py", line 173, in do_scrape
    report[scraper_name] = scraper.do_scrape(**scrape_args)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py", line 116, in do_scrape
    self.save_object(obj)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py", line 99, in save_object
    raise ve
  File "/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py", line 96, in save_object
    obj.validate()
  File "/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py", line 191, in validate
    self.__class__.__name__, self._id, '\n\t'+'\n\t'.join(errors)
pupa.exceptions.ScrapeValueError: validation of Person e21c0558-5681-11e8-a8aa-029b97b45e2a failed: 
	'/booher' is not a 'uri'
Failed validating 'format' in schema['properties']['links']['items']['properties']['url']:
    {'format': 'uri', 'type': 'string'}
On instance['links'][0]['url']:
    '/booher'
	'/booher' is not a 'uri'
Failed validating 'format' in schema['properties']['sources']['items']['properties']['url']:
    {'format': 'uri', 'type': 'string'}
On instance['sources'][0]['url']:
    '/booher'
```

Visit http://bobsled.openstates.org for more info.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `openstates/mi/people.py`
Content:
```
1 import re
2 import requests
3 
4 import lxml.html
5 import scrapelib
6 from pupa.scrape import Person, Scraper
7 
8 
9 abbr = {'D': 'Democratic', 'R': 'Republican'}
10 
11 
12 class MIPersonScraper(Scraper):
13     def scrape(self, chamber=None, session=None):
14         if chamber == 'upper':
15             yield from self.scrape_upper(chamber)
16         elif chamber == 'lower':
17             yield from self.scrape_lower(chamber)
18         else:
19             yield from self.scrape_upper(chamber)
20             yield from self.scrape_lower(chamber)
21 
22     def scrape_lower(self, chamber):
23         url = 'http://www.house.mi.gov/mhrpublic/frmRepList.aspx'
24         table = [
25             "website",
26             "district",
27             "name",
28             "party",
29             "location",
30             "phone",
31             "email"
32         ]
33 
34         data = self.get(url).text
35         doc = lxml.html.fromstring(data)
36 
37         # skip two rows at top
38         for row in doc.xpath('//table[@id="grvRepInfo"]/*'):
39             tds = row.xpath('.//td')
40             if len(tds) == 0:
41                 continue
42             metainf = {}
43             for i in range(0, len(table)):
44                 metainf[table[i]] = tds[i]
45             district = str(int(metainf['district'].text_content().strip()))
46             party = metainf['party'].text_content().strip()
47             phone = metainf['phone'].text_content().strip()
48             email = metainf['email'].text_content().strip()
49             name = metainf['name'].text_content().strip()
50             if name == 'Vacant' or re.match(r'^District \d{1,3}$', name):
51                 self.warning('District {} appears vacant, and will be skipped'.format(district))
52                 continue
53             leg_url = metainf['website'].xpath("./a")[0].attrib['href']
54 
55             office = metainf['location'].text_content().strip()
56             office = re.sub(
57                 ' HOB',
58                 ' Anderson House Office Building\n124 North Capitol Avenue\nLansing, MI 48933',
59                 office
60             )
61             office = re.sub(
62                 ' CB',
63                 ' State Capitol Building\nLansing, MI 48909',
64                 office
65             )
66 
67             try:
68                 photo_url = self.get_photo_url(leg_url)[0]
69             except (scrapelib.HTTPError, IndexError):
70                 photo_url = ''
71                 self.warning('no photo url for %s', name)
72 
73             person = Person(name=name, district=district, party=abbr[party],
74                             primary_org='lower', image=photo_url)
75 
76             person.add_link(leg_url)
77             person.add_source(leg_url)
78 
79             person.add_contact_detail(type='address', value=office, note='Capitol Office')
80             person.add_contact_detail(type='voice', value=phone, note='Capitol Office')
81             person.add_contact_detail(type='email', value=email, note='Capitol Office')
82 
83             yield person
84 
85     def scrape_upper(self, chamber):
86         url = 'http://www.senate.michigan.gov/senatorinfo_list.html'
87         url_to_append = 'http://www.senate.michigan.gov/_images/'
88         data = self.get(url).text
89         doc = lxml.html.fromstring(data)
90         for row in doc.xpath('//table[not(@class="calendar")]//tr')[3:]:
91             if len(row) != 7:
92                 continue
93 
94             # party, dist, member, office_phone, office_fax, office_loc
95             party, dist, member, contact, phone, fax, loc = row.getchildren()
96             if (party.text_content().strip() == "" or
97                     'Lieutenant Governor' in member.text_content()):
98                 continue
99 
100             party = abbr[party.text]
101             district = dist.text_content().strip()
102             name = member.text_content().strip()
103             name = re.sub(r'\s+', " ", name)
104             surname = re.split(', | ', name)
105             surname[0] = re.sub('[\']', '', surname[0])
106             try:
107                 self.head(url_to_append + surname[0] + '.png')
108                 photo_url = url_to_append + surname[0] + '.png'
109             except scrapelib.HTTPError:
110                 try:
111                     self.head(url_to_append + surname[0] + '.jpg')
112                     photo_url = url_to_append + surname[0] + '.jpg'
113                 except scrapelib.HTTPError:
114                     photo_url = None
115 
116             if name == 'Vacant':
117                 self.info('district %s is vacant', district)
118                 continue
119 
120             leg_url = member.xpath('a/@href')[0]
121             office_phone = phone.text
122             office_fax = fax.text
123 
124             office_loc = loc.text
125             office_loc = re.sub(
126                 ' Farnum Bldg',
127                 ' Farnum Office Building\n125 West Allegan Street\nLansing, MI 48933',
128                 office_loc
129             )
130             office_loc = re.sub(
131                 ' Capitol Bldg',
132                 ' State Capitol Building\nLansing, MI 48909',
133                 office_loc
134             )
135 
136             # email addresses aren't on the list page anymore but they
137             # are on the page linked off "Contact Me"
138 
139             # data has a typo in a row
140             email = None
141             contact_url = [
142                 a for a in row.xpath(".//a")
143                 if a.text in ('Contact Me', 'Conact Me')][0].get('href')
144             try:
145                 contact_html = self.get(contact_url).text
146                 contact_doc = lxml.html.fromstring(contact_html)
147 
148                 header_email = contact_doc.xpath("//a[@class='header_email']")
149                 if header_email:
150                     email = header_email[0].text
151                 else:
152                     # not using the most common template, but maybe they
153                     # dropped their email on the page somewhere
154                     links = contact_doc.xpath('//a') or []
155                     text_email = [a for a in links
156                                   if 'mailto:' in (a.get('href') or '')]
157                     if text_email:
158                         email = text_email[0].text
159             except requests.exceptions.TooManyRedirects:
160                 self.warning("Contact Link Not Working for %s" % name)
161             person = Person(name=name, district=district, party=party,
162                             primary_org='upper', image=photo_url)
163 
164             person.add_link(leg_url)
165             person.add_source(leg_url)
166 
167             person.add_contact_detail(type='address', value=office_loc, note='Capitol Office')
168             person.add_contact_detail(type='voice', value=office_phone, note='Capitol Office')
169             person.add_contact_detail(type='fax', value=office_fax, note='Capitol Office')
170             if email:
171                 person.add_contact_detail(type='email', value=email, note='Capitol Office')
172 
173             yield person
174 
175     def get_photo_url(self, url):
176         data = self.get(url).text
177         doc = lxml.html.fromstring(data)
178         doc.make_links_absolute(url)
179         return (
180             doc.xpath('//div[contains(@class, "headshotTop")]//img/@src') +  # housedems.com
181             doc.xpath('//div[contains(@class, "widget_sp_image")]//img/@src')  # gophouse.org
182         )
183
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/openstates/mi/people.py b/openstates/mi/people.py
--- a/openstates/mi/people.py
+++ b/openstates/mi/people.py
@@ -87,6 +87,7 @@
         url_to_append = 'http://www.senate.michigan.gov/_images/'
         data = self.get(url).text
         doc = lxml.html.fromstring(data)
+        doc.make_links_absolute(url)
         for row in doc.xpath('//table[not(@class="calendar")]//tr')[3:]:
             if len(row) != 7:
                 continue

{"golden_diff": "diff --git a/openstates/mi/people.py b/openstates/mi/people.py\n--- a/openstates/mi/people.py\n+++ b/openstates/mi/people.py\n@@ -87,6 +87,7 @@\n         url_to_append = 'http://www.senate.michigan.gov/_images/'\n         data = self.get(url).text\n         doc = lxml.html.fromstring(data)\n+        doc.make_links_absolute(url)\n         for row in doc.xpath('//table[not(@class=\"calendar\")]//tr')[3:]:\n             if len(row) != 7:\n                 continue\n", "issue": "MI failing since at least 2018-05-12\nMI has been failing since 2018-05-12\n\nBased on automated runs it appears that MI has not run successfully in 2 days (2018-05-12).\n\n\n```\n  02:47:18 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2018-HJR-GG\n02:47:19 INFO pupa: save bill HJR GG in 2017-2018 as bill_dcf34e60-5681-11e8-a8aa-029b97b45e2a.json\n02:47:19 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2017-HJR-HH\n02:47:20 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2018-HJR-HH\n02:47:21 INFO pupa: save bill HJR HH in 2017-2018 as bill_de254248-5681-11e8-a8aa-029b97b45e2a.json\n02:47:21 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2017-HJR-II\n02:47:22 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2018-HJR-II\n02:47:23 INFO pupa: save bill HJR II in 2017-2018 as bill_df57e738-5681-11e8-a8aa-029b97b45e2a.json\n02:47:23 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2017-HJR-JJ\n02:47:24 INFO scrapelib: GET - http://legislature.mi.gov/doc.aspx?2018-HJR-JJ\n02:47:25 WARNING pupa: Cannot open bill page for HJR JJ; skipping\n02:47:25 INFO scrapelib: GET - http://www.senate.michigan.gov/senatorinfo_list.html\n02:47:25 INFO scrapelib: HEAD - http://www.senate.michigan.gov/_images/Booher.png\n02:47:26 INFO scrapelib: GET - http://www.SenatorDarwinBooher.com/contact/\n02:47:27 INFO pupa: save person Booher, Darwin L as person_e21c0558-5681-11e8-a8aa-029b97b45e2a.json\nloaded Open States pupa settings...\nmi (scrape, import)\n  bills: {}\n  people: {}\n  committees: {}\nTraceback (most recent call last):\n  File \"/opt/openstates/venv-pupa//bin/pupa\", line 11, in <module>\n    load_entry_point('pupa', 'console_scripts', 'pupa')()\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/cli/__main__.py\", line 68, in main\n    subcommands[args.subcommand].handle(args, other)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py\", line 260, in handle\n    return self.do_handle(args, other, juris)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py\", line 305, in do_handle\n    report['scrape'] = self.do_scrape(juris, args, scrapers)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py\", line 173, in do_scrape\n    report[scraper_name] = scraper.do_scrape(**scrape_args)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py\", line 116, in do_scrape\n    self.save_object(obj)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py\", line 99, in save_object\n    raise ve\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py\", line 96, in save_object\n    obj.validate()\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py\", line 191, in validate\n    self.__class__.__name__, self._id, '\\n\\t'+'\\n\\t'.join(errors)\npupa.exceptions.ScrapeValueError: validation of Person e21c0558-5681-11e8-a8aa-029b97b45e2a failed: \n\t'/booher' is not a 'uri'\nFailed validating 'format' in schema['properties']['links']['items']['properties']['url']:\n    {'format': 'uri', 'type': 'string'}\nOn instance['links'][0]['url']:\n    '/booher'\n\t'/booher' is not a 'uri'\nFailed validating 'format' in schema['properties']['sources']['items']['properties']['url']:\n    {'format': 'uri', 'type': 'string'}\nOn instance['sources'][0]['url']:\n    '/booher'\n```\n\nVisit http://bobsled.openstates.org for more info.\n\n", "before_files": [{"content": "import re\nimport requests\n\nimport lxml.html\nimport scrapelib\nfrom pupa.scrape import Person, Scraper\n\n\nabbr = {'D': 'Democratic', 'R': 'Republican'}\n\n\nclass MIPersonScraper(Scraper):\n    def scrape(self, chamber=None, session=None):\n        if chamber == 'upper':\n            yield from self.scrape_upper(chamber)\n        elif chamber == 'lower':\n            yield from self.scrape_lower(chamber)\n        else:\n            yield from self.scrape_upper(chamber)\n            yield from self.scrape_lower(chamber)\n\n    def scrape_lower(self, chamber):\n        url = 'http://www.house.mi.gov/mhrpublic/frmRepList.aspx'\n        table = [\n            \"website\",\n            \"district\",\n            \"name\",\n            \"party\",\n            \"location\",\n            \"phone\",\n            \"email\"\n        ]\n\n        data = self.get(url).text\n        doc = lxml.html.fromstring(data)\n\n        # skip two rows at top\n        for row in doc.xpath('//table[@id=\"grvRepInfo\"]/*'):\n            tds = row.xpath('.//td')\n            if len(tds) == 0:\n                continue\n            metainf = {}\n            for i in range(0, len(table)):\n                metainf[table[i]] = tds[i]\n            district = str(int(metainf['district'].text_content().strip()))\n            party = metainf['party'].text_content().strip()\n            phone = metainf['phone'].text_content().strip()\n            email = metainf['email'].text_content().strip()\n            name = metainf['name'].text_content().strip()\n            if name == 'Vacant' or re.match(r'^District \\d{1,3}$', name):\n                self.warning('District {} appears vacant, and will be skipped'.format(district))\n                continue\n            leg_url = metainf['website'].xpath(\"./a\")[0].attrib['href']\n\n            office = metainf['location'].text_content().strip()\n            office = re.sub(\n                ' HOB',\n                ' Anderson House Office Building\\n124 North Capitol Avenue\\nLansing, MI 48933',\n                office\n            )\n            office = re.sub(\n                ' CB',\n                ' State Capitol Building\\nLansing, MI 48909',\n                office\n            )\n\n            try:\n                photo_url = self.get_photo_url(leg_url)[0]\n            except (scrapelib.HTTPError, IndexError):\n                photo_url = ''\n                self.warning('no photo url for %s', name)\n\n            person = Person(name=name, district=district, party=abbr[party],\n                            primary_org='lower', image=photo_url)\n\n            person.add_link(leg_url)\n            person.add_source(leg_url)\n\n            person.add_contact_detail(type='address', value=office, note='Capitol Office')\n            person.add_contact_detail(type='voice', value=phone, note='Capitol Office')\n            person.add_contact_detail(type='email', value=email, note='Capitol Office')\n\n            yield person\n\n    def scrape_upper(self, chamber):\n        url = 'http://www.senate.michigan.gov/senatorinfo_list.html'\n        url_to_append = 'http://www.senate.michigan.gov/_images/'\n        data = self.get(url).text\n        doc = lxml.html.fromstring(data)\n        for row in doc.xpath('//table[not(@class=\"calendar\")]//tr')[3:]:\n            if len(row) != 7:\n                continue\n\n            # party, dist, member, office_phone, office_fax, office_loc\n            party, dist, member, contact, phone, fax, loc = row.getchildren()\n            if (party.text_content().strip() == \"\" or\n                    'Lieutenant Governor' in member.text_content()):\n                continue\n\n            party = abbr[party.text]\n            district = dist.text_content().strip()\n            name = member.text_content().strip()\n            name = re.sub(r'\\s+', \" \", name)\n            surname = re.split(', | ', name)\n            surname[0] = re.sub('[\\']', '', surname[0])\n            try:\n                self.head(url_to_append + surname[0] + '.png')\n                photo_url = url_to_append + surname[0] + '.png'\n            except scrapelib.HTTPError:\n                try:\n                    self.head(url_to_append + surname[0] + '.jpg')\n                    photo_url = url_to_append + surname[0] + '.jpg'\n                except scrapelib.HTTPError:\n                    photo_url = None\n\n            if name == 'Vacant':\n                self.info('district %s is vacant', district)\n                continue\n\n            leg_url = member.xpath('a/@href')[0]\n            office_phone = phone.text\n            office_fax = fax.text\n\n            office_loc = loc.text\n            office_loc = re.sub(\n                ' Farnum Bldg',\n                ' Farnum Office Building\\n125 West Allegan Street\\nLansing, MI 48933',\n                office_loc\n            )\n            office_loc = re.sub(\n                ' Capitol Bldg',\n                ' State Capitol Building\\nLansing, MI 48909',\n                office_loc\n            )\n\n            # email addresses aren't on the list page anymore but they\n            # are on the page linked off \"Contact Me\"\n\n            # data has a typo in a row\n            email = None\n            contact_url = [\n                a for a in row.xpath(\".//a\")\n                if a.text in ('Contact Me', 'Conact Me')][0].get('href')\n            try:\n                contact_html = self.get(contact_url).text\n                contact_doc = lxml.html.fromstring(contact_html)\n\n                header_email = contact_doc.xpath(\"//a[@class='header_email']\")\n                if header_email:\n                    email = header_email[0].text\n                else:\n                    # not using the most common template, but maybe they\n                    # dropped their email on the page somewhere\n                    links = contact_doc.xpath('//a') or []\n                    text_email = [a for a in links\n                                  if 'mailto:' in (a.get('href') or '')]\n                    if text_email:\n                        email = text_email[0].text\n            except requests.exceptions.TooManyRedirects:\n                self.warning(\"Contact Link Not Working for %s\" % name)\n            person = Person(name=name, district=district, party=party,\n                            primary_org='upper', image=photo_url)\n\n            person.add_link(leg_url)\n            person.add_source(leg_url)\n\n            person.add_contact_detail(type='address', value=office_loc, note='Capitol Office')\n            person.add_contact_detail(type='voice', value=office_phone, note='Capitol Office')\n            person.add_contact_detail(type='fax', value=office_fax, note='Capitol Office')\n            if email:\n                person.add_contact_detail(type='email', value=email, note='Capitol Office')\n\n            yield person\n\n    def get_photo_url(self, url):\n        data = self.get(url).text\n        doc = lxml.html.fromstring(data)\n        doc.make_links_absolute(url)\n        return (\n            doc.xpath('//div[contains(@class, \"headshotTop\")]//img/@src') +  # housedems.com\n            doc.xpath('//div[contains(@class, \"widget_sp_image\")]//img/@src')  # gophouse.org\n        )\n", "path": "openstates/mi/people.py"}], "after_files": [{"content": "import re\nimport requests\n\nimport lxml.html\nimport scrapelib\nfrom pupa.scrape import Person, Scraper\n\n\nabbr = {'D': 'Democratic', 'R': 'Republican'}\n\n\nclass MIPersonScraper(Scraper):\n    def scrape(self, chamber=None, session=None):\n        if chamber == 'upper':\n            yield from self.scrape_upper(chamber)\n        elif chamber == 'lower':\n            yield from self.scrape_lower(chamber)\n        else:\n            yield from self.scrape_upper(chamber)\n            yield from self.scrape_lower(chamber)\n\n    def scrape_lower(self, chamber):\n        url = 'http://www.house.mi.gov/mhrpublic/frmRepList.aspx'\n        table = [\n            \"website\",\n            \"district\",\n            \"name\",\n            \"party\",\n            \"location\",\n            \"phone\",\n            \"email\"\n        ]\n\n        data = self.get(url).text\n        doc = lxml.html.fromstring(data)\n\n        # skip two rows at top\n        for row in doc.xpath('//table[@id=\"grvRepInfo\"]/*'):\n            tds = row.xpath('.//td')\n            if len(tds) == 0:\n                continue\n            metainf = {}\n            for i in range(0, len(table)):\n                metainf[table[i]] = tds[i]\n            district = str(int(metainf['district'].text_content().strip()))\n            party = metainf['party'].text_content().strip()\n            phone = metainf['phone'].text_content().strip()\n            email = metainf['email'].text_content().strip()\n            name = metainf['name'].text_content().strip()\n            if name == 'Vacant' or re.match(r'^District \\d{1,3}$', name):\n                self.warning('District {} appears vacant, and will be skipped'.format(district))\n                continue\n            leg_url = metainf['website'].xpath(\"./a\")[0].attrib['href']\n\n            office = metainf['location'].text_content().strip()\n            office = re.sub(\n                ' HOB',\n                ' Anderson House Office Building\\n124 North Capitol Avenue\\nLansing, MI 48933',\n                office\n            )\n            office = re.sub(\n                ' CB',\n                ' State Capitol Building\\nLansing, MI 48909',\n                office\n            )\n\n            try:\n                photo_url = self.get_photo_url(leg_url)[0]\n            except (scrapelib.HTTPError, IndexError):\n                photo_url = ''\n                self.warning('no photo url for %s', name)\n\n            person = Person(name=name, district=district, party=abbr[party],\n                            primary_org='lower', image=photo_url)\n\n            person.add_link(leg_url)\n            person.add_source(leg_url)\n\n            person.add_contact_detail(type='address', value=office, note='Capitol Office')\n            person.add_contact_detail(type='voice', value=phone, note='Capitol Office')\n            person.add_contact_detail(type='email', value=email, note='Capitol Office')\n\n            yield person\n\n    def scrape_upper(self, chamber):\n        url = 'http://www.senate.michigan.gov/senatorinfo_list.html'\n        url_to_append = 'http://www.senate.michigan.gov/_images/'\n        data = self.get(url).text\n        doc = lxml.html.fromstring(data)\n        doc.make_links_absolute(url)\n        for row in doc.xpath('//table[not(@class=\"calendar\")]//tr')[3:]:\n            if len(row) != 7:\n                continue\n\n            # party, dist, member, office_phone, office_fax, office_loc\n            party, dist, member, contact, phone, fax, loc = row.getchildren()\n            if (party.text_content().strip() == \"\" or\n                    'Lieutenant Governor' in member.text_content()):\n                continue\n\n            party = abbr[party.text]\n            district = dist.text_content().strip()\n            name = member.text_content().strip()\n            name = re.sub(r'\\s+', \" \", name)\n            surname = re.split(', | ', name)\n            surname[0] = re.sub('[\\']', '', surname[0])\n            try:\n                self.head(url_to_append + surname[0] + '.png')\n                photo_url = url_to_append + surname[0] + '.png'\n            except scrapelib.HTTPError:\n                try:\n                    self.head(url_to_append + surname[0] + '.jpg')\n                    photo_url = url_to_append + surname[0] + '.jpg'\n                except scrapelib.HTTPError:\n                    photo_url = None\n\n            if name == 'Vacant':\n                self.info('district %s is vacant', district)\n                continue\n\n            leg_url = member.xpath('a/@href')[0]\n            office_phone = phone.text\n            office_fax = fax.text\n\n            office_loc = loc.text\n            office_loc = re.sub(\n                ' Farnum Bldg',\n                ' Farnum Office Building\\n125 West Allegan Street\\nLansing, MI 48933',\n                office_loc\n            )\n            office_loc = re.sub(\n                ' Capitol Bldg',\n                ' State Capitol Building\\nLansing, MI 48909',\n                office_loc\n            )\n\n            # email addresses aren't on the list page anymore but they\n            # are on the page linked off \"Contact Me\"\n\n            # data has a typo in a row\n            email = None\n            contact_url = [\n                a for a in row.xpath(\".//a\")\n                if a.text in ('Contact Me', 'Conact Me')][0].get('href')\n            try:\n                contact_html = self.get(contact_url).text\n                contact_doc = lxml.html.fromstring(contact_html)\n\n                header_email = contact_doc.xpath(\"//a[@class='header_email']\")\n                if header_email:\n                    email = header_email[0].text\n                else:\n                    # not using the most common template, but maybe they\n                    # dropped their email on the page somewhere\n                    links = contact_doc.xpath('//a') or []\n                    text_email = [a for a in links\n                                  if 'mailto:' in (a.get('href') or '')]\n                    if text_email:\n                        email = text_email[0].text\n            except requests.exceptions.TooManyRedirects:\n                self.warning(\"Contact Link Not Working for %s\" % name)\n            person = Person(name=name, district=district, party=party,\n                            primary_org='upper', image=photo_url)\n\n            person.add_link(leg_url)\n            person.add_source(leg_url)\n\n            person.add_contact_detail(type='address', value=office_loc, note='Capitol Office')\n            person.add_contact_detail(type='voice', value=office_phone, note='Capitol Office')\n            person.add_contact_detail(type='fax', value=office_fax, note='Capitol Office')\n            if email:\n                person.add_contact_detail(type='email', value=email, note='Capitol Office')\n\n            yield person\n\n    def get_photo_url(self, url):\n        data = self.get(url).text\n        doc = lxml.html.fromstring(data)\n        doc.make_links_absolute(url)\n        return (\n            doc.xpath('//div[contains(@class, \"headshotTop\")]//img/@src') +  # housedems.com\n            doc.xpath('//div[contains(@class, \"widget_sp_image\")]//img/@src')  # gophouse.org\n        )\n", "path": "openstates/mi/people.py"}]}